GPUImage源碼閱讀(一)

概述

GPUImage是一個著名的圖像處理開源庫,它讓你能夠在圖片、視頻柱衔、相機上使用GPU加速的濾鏡和其它特效。與CoreImage框架相比愉棱,可以根據(jù)GPUImage提供的接口唆铐,使用自定義的濾鏡。項目地址:https://github.com/BradLarson/GPUImage
這篇文章主要是閱讀GPUImage框架中的 GLProgram奔滑、GPUImageContext 兩個重要類的源碼或链。這兩個類是 GPUImage 框架的基礎(chǔ),里面涉及的知識也有 OpenGL ES 基礎(chǔ) 和 多線程 基礎(chǔ)档押。以下是源碼內(nèi)容:
GLProgram
GPUImageContext

基礎(chǔ)

閱讀GPUImage源碼需要一定的知識儲備,在這里我列出了需要的一些基礎(chǔ)知識:OpenGL ES 2.0AVFoundation令宿、CoreGraphics叼耙。如果對以上框架不熟悉,可以學(xué)習(xí)一下相關(guān)的知識粒没。在之前我寫了一些 OpenGL專題 http://www.reibang.com/c/30e2e76bc140 相關(guān)的專題系列筛婉,大家也可以去學(xué)習(xí)下。

GLProgram

之前在學(xué)習(xí)OpenGL的時候癞松,我們首先要需要做的就是初始化OpenGL ES環(huán)境爽撒,編譯、鏈接頂點著色器和片元著色器响蓉。在GPUImage中封裝了GLProgram專門處理OpenGL ES程序的創(chuàng)建等相關(guān)工作硕勿。
GLProgram的方法不是很多,這里簡單介紹:

  • 初始化方法枫甲,可以根據(jù)需要傳入頂點著色器的路徑或字符串以及片源著色器的路徑及字符串進行初始化源武。
- (id)initWithVertexShaderString:(NSString *)vShaderString 
            fragmentShaderString:(NSString *)fShaderString;
- (id)initWithVertexShaderString:(NSString *)vShaderString 
          fragmentShaderFilename:(NSString *)fShaderFilename;
- (id)initWithVertexShaderFilename:(NSString *)vShaderFilename 
            fragmentShaderFilename:(NSString *)fShaderFilename;

初始化的過程包含了頂點片源著色器的創(chuàng)建、編譯想幻,著色器程序的創(chuàng)建粱栖,頂點片源著色器附著到著色器程序等過程。

- (id)initWithVertexShaderString:(NSString *)vShaderString 
            fragmentShaderString:(NSString *)fShaderString;
{
    if ((self = [super init])) 
    {
        _initialized = NO;
        
        // 初始化屬性數(shù)組
        attributes = [[NSMutableArray alloc] init];
        // 初始化uniform屬性數(shù)組
        uniforms = [[NSMutableArray alloc] init];
        
        // 創(chuàng)建著色器程序
        program = glCreateProgram();
        
        // 編譯頂點著色器
        if (![self compileShader:&vertShader 
                            type:GL_VERTEX_SHADER 
                          string:vShaderString])
        {
            NSLog(@"Failed to compile vertex shader");
        }
        
        // 編譯片源著色器
        // Create and compile fragment shader
        if (![self compileShader:&fragShader 
                            type:GL_FRAGMENT_SHADER 
                          string:fShaderString])
        {
            NSLog(@"Failed to compile fragment shader");
        }
        
        // 將頂點片源著色器附著到著色器程序
        glAttachShader(program, vertShader);
        glAttachShader(program, fragShader);
    }
    
    return self;
}
  • 鏈接程序脏毯,和編譯型語言一樣,OpenGL程序也需要鏈接食店。
- (BOOL)link
{
//    CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();

    GLint status;
    
    // 鏈接著色器程序
    glLinkProgram(program);
    
    // 獲取鏈接狀態(tài)
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    // 鏈接失敗則返回
    if (status == GL_FALSE)
        return NO;
    
    // 鏈接成功,就可以刪掉相關(guān)的shader,釋放資源
    if (vertShader)
    {
        glDeleteShader(vertShader);
        vertShader = 0;
    }
    if (fragShader)
    {
        glDeleteShader(fragShader);
        fragShader = 0;
    }
    
    // 設(shè)置初始化成功標(biāo)識
    self.initialized = YES;

//    CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
//    NSLog(@"Linked in %f ms", linkTime * 1000.0);

    return YES;
}
  • 使用著色器程序
- (void)use
{
    glUseProgram(program);
}
  • 在給著色器傳值的時候椒功,我們需要獲取相關(guān)屬性的位置丁屎。GLProgram也提供了相關(guān)接口证九,方便我們獲取變量的位置愧怜。
- (void)addAttribute:(NSString *)attributeName
{
    // 先判斷當(dāng)前的屬性是否已存在
    if (![attributes containsObject:attributeName])
    {
        // 如果不存在先加入屬性數(shù)組尘分,然后綁定該屬性的位置為在屬性數(shù)組中的位置
        [attributes addObject:attributeName];
        glBindAttribLocation(program, 
                             (GLuint)[attributes indexOfObject:attributeName],
                             [attributeName UTF8String]);
    }
}
// END:addattribute
// START:indexmethods
- (GLuint)attributeIndex:(NSString *)attributeName
{
    // 獲取著色器屬性變量的位置,即在數(shù)組的位置(根據(jù)之前的綁定關(guān)系)
    return (GLuint)[attributes indexOfObject:attributeName];
}
- (GLuint)uniformIndex:(NSString *)uniformName
{
    // 獲取Uniform變量的位置
    return glGetUniformLocation(program, [uniformName UTF8String]);
}
  • 釋放資源。在析構(gòu)的時候,將著色器等相關(guān)資源清理。
- (void)dealloc
{
    if (vertShader)
        glDeleteShader(vertShader);
        
    if (fragShader)
        glDeleteShader(fragShader);
    
    if (program)
        glDeleteProgram(program);
       
}

GPUImageContext

GPUImageContext類,提供OpenGL ES基本上下文偿曙,GPUImage相關(guān)處理線程竿秆,GLProgram緩存歉备、幀緩存。由于是上下文對象,因此該模塊提供的更多是存取享甸、設(shè)置相關(guān)的方法。

  • 屬性列表
// GPUImage處理OpenGL繪制的相關(guān)隊列,串行隊列
@property(readonly, nonatomic) dispatch_queue_t contextQueue;
// 當(dāng)前使用的著色器程序
@property(readwrite, retain, nonatomic) GLProgram *currentShaderProgram;
// OpenGLES上下文對象
@property(readonly, retain, nonatomic) EAGLContext *context;
// CoreVideo中的紋理緩存
@property(readonly) CVOpenGLESTextureCacheRef coreVideoTextureCache;
// 幀緩存
@property(readonly) GPUImageFramebufferCache *framebufferCache;
  • 初始化過程。在初始化的過程中通過dispatch_queue_set_specific設(shè)置隊列標(biāo)識谈秫,這樣做的原因在Effective Objective-C 2.0 中有提到,或者參見我之前的博客 重拾Effective Objective-C 2.0熱點問題
- (id)init;
{
    if (!(self = [super init]))
    {
        return nil;
    }
    
    // 創(chuàng)建OpenGL渲染隊列
    openGLESContextQueueKey = &openGLESContextQueueKey;
    _contextQueue = dispatch_queue_create("com.sunsetlakesoftware.GPUImage.openGLESContextQueue", GPUImageDefaultQueueAttribute());
    
#if OS_OBJECT_USE_OBJC
    // 設(shè)置隊列標(biāo)識
    dispatch_queue_set_specific(_contextQueue, openGLESContextQueueKey, (__bridge void *)self, NULL);
#endif
    // 初始化著色器緩存相關(guān)數(shù)組
    shaderProgramCache = [[NSMutableDictionary alloc] init];
    shaderProgramUsageHistory = [[NSMutableArray alloc] init];
    
    return self;
}
  • 方法列表
// 獲取隊列標(biāo)識
+ (void *)contextKey;
// 單例對象
+ (GPUImageContext *)sharedImageProcessingContext;
// 獲取處理隊列
+ (dispatch_queue_t)sharedContextQueue;
// 幀緩存
+ (GPUImageFramebufferCache *)sharedFramebufferCache;
// 設(shè)置當(dāng)前上下文
+ (void)useImageProcessingContext;
- (void)useAsCurrentContext;
// 設(shè)置當(dāng)前的GL程序
+ (void)setActiveShaderProgram:(GLProgram *)shaderProgram;
- (void)setContextShaderProgram:(GLProgram *)shaderProgram;
// 獲取設(shè)備OpenGLES相關(guān)特性的支持情況
+ (GLint)maximumTextureSizeForThisDevice;
+ (GLint)maximumTextureUnitsForThisDevice;
+ (GLint)maximumVaryingVectorsForThisDevice;
+ (BOOL)deviceSupportsOpenGLESExtension:(NSString *)extension;
+ (BOOL)deviceSupportsRedTextures;
+ (BOOL)deviceSupportsFramebufferReads;
// 紋理大小調(diào)整,保證紋理不超過OpenGLES支持最大的尺寸
+ (CGSize)sizeThatFitsWithinATextureForSize:(CGSize)inputSize;
// 將渲染緩存呈現(xiàn)在設(shè)備上
- (void)presentBufferForDisplay;
// 創(chuàng)建GLProgram奢米,首先在緩存中查找谒拴,如果沒有則創(chuàng)建
- (GLProgram *)programForVertexShaderString:(NSString *)vertexShaderString fragmentShaderString:(NSString *)fragmentShaderString;
// 創(chuàng)建Sharegroup
- (void)useSharegroup:(EAGLSharegroup *)sharegroup;
// Manage fast texture upload
+ (BOOL)supportsFastTextureUpload;
@end

由于GPUImageContext相當(dāng)于一個上下文對象,主要是管理其它的對象涉波,因此該類沒有太多復(fù)雜的業(yè)務(wù)英上,這里主要看一下幾個方法:

  • 調(diào)整紋理大小,保證紋理不超過OpenGLES支持最大的尺寸:
+ (CGSize)sizeThatFitsWithinATextureForSize:(CGSize)inputSize;
{
    GLint maxTextureSize = [self maximumTextureSizeForThisDevice]; 
    if ( (inputSize.width < maxTextureSize) && (inputSize.height < maxTextureSize) )
    {
        return inputSize;
    }
    
    CGSize adjustedSize;
    if (inputSize.width > inputSize.height)
    {
        adjustedSize.width = (CGFloat)maxTextureSize;
        adjustedSize.height = ((CGFloat)maxTextureSize / inputSize.width) * inputSize.height;
    }
    else
    {
        adjustedSize.height = (CGFloat)maxTextureSize;
        adjustedSize.width = ((CGFloat)maxTextureSize / inputSize.height) * inputSize.width;
    }

    return adjustedSize;
}
  • 獲取OpenGLES支持的最大紋理尺寸啤覆。
+ (GLint)maximumTextureSizeForThisDevice;
{
    static dispatch_once_t pred;
    static GLint maxTextureSize = 0;
    
    dispatch_once(&pred, ^{
        [self useImageProcessingContext];
        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
    });

    return maxTextureSize;
}
  • 創(chuàng)建GLProgram苍日,首先在緩存中查找,如果沒有則創(chuàng)建
- (GLProgram *)programForVertexShaderString:(NSString *)vertexShaderString fragmentShaderString:(NSString *)fragmentShaderString;
{
    NSString *lookupKeyForShaderProgram = [NSString stringWithFormat:@"V: %@ - F: %@", vertexShaderString, fragmentShaderString];
    GLProgram *programFromCache = [shaderProgramCache objectForKey:lookupKeyForShaderProgram];

    if (programFromCache == nil)
    {
        programFromCache = [[GLProgram alloc] initWithVertexShaderString:vertexShaderString fragmentShaderString:fragmentShaderString];
        [shaderProgramCache setObject:programFromCache forKey:lookupKeyForShaderProgram];
//        [shaderProgramUsageHistory addObject:lookupKeyForShaderProgram];
//        if ([shaderProgramUsageHistory count] >= MAXSHADERPROGRAMSALLOWEDINCACHE)
//        {
//            for (NSUInteger currentShaderProgramRemovedFromCache = 0; currentShaderProgramRemovedFromCache < 10; currentShaderProgramRemovedFromCache++)
//            {
//                NSString *shaderProgramToRemoveFromCache = [shaderProgramUsageHistory objectAtIndex:0];
//                [shaderProgramUsageHistory removeObjectAtIndex:0];
//                [shaderProgramCache removeObjectForKey:shaderProgramToRemoveFromCache];
//            }
//        }
    }
    
    return programFromCache;
}
  • 創(chuàng)建EAGLContext上下文對象窗声,使用的是kEAGLRenderingAPIOpenGLES2的API也就是OpenGL ES 2.0相恃。
- (EAGLContext *)createContext;
{
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2 sharegroup:_sharegroup];
    NSAssert(context != nil, @"Unable to create an OpenGL ES 2.0 context. The GPUImage framework requires OpenGL ES 2.0 support to work.");
    return context;
}
  • 設(shè)置當(dāng)前的上下文對象,以及當(dāng)前的著色器程序笨觅。
- (void)setContextShaderProgram:(GLProgram *)shaderProgram;
{
    EAGLContext *imageProcessingContext = [self context];
    if ([EAGLContext currentContext] != imageProcessingContext)
    {
        [EAGLContext setCurrentContext:imageProcessingContext];
    }
    
    if (self.currentShaderProgram != shaderProgram)
    {
        self.currentShaderProgram = shaderProgram;
        [shaderProgram use];
    }
}

總結(jié)

GLProgram 與著色器程序創(chuàng)建息息相關(guān)拦耐,其中包括創(chuàng)建、編譯见剩、鏈接杀糯、使用等過程。
GPUImageContext 是GPUImage的上下文對象炮温,管理著OpenGLES上下文對象火脉,管理著GL程序,管理著幀緩存等GPUImage中的基本組件柒啤。

源碼地址:GPUImage源碼閱讀系列 https://github.com/QinminiOS/GPUImage
系列文章地址:GPUImage源碼閱讀 http://www.reibang.com/nb/11749791

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末倦挂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子担巩,更是在濱河造成了極大的恐慌方援,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涛癌,死亡現(xiàn)場離奇詭異犯戏,居然都是意外死亡,警方通過查閱死者的電腦和手機拳话,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門先匪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弃衍,你說我怎么就攤上這事呀非。” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵岸裙,是天一觀的道長猖败。 經(jīng)常有香客問我,道長降允,這世上最難降的妖魔是什么恩闻? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮剧董,結(jié)果婚禮上幢尚,老公的妹妹穿的比我還像新娘。我一直安慰自己翅楼,他們只是感情好侠草,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著犁嗅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晤碘。 梳的紋絲不亂的頭發(fā)上褂微,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音园爷,去河邊找鬼宠蚂。 笑死,一個胖子當(dāng)著我的面吹牛童社,可吹牛的內(nèi)容都是我干的求厕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼扰楼,長吁一口氣:“原來是場噩夢啊……” “哼呀癣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弦赖,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤项栏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蹬竖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沼沈,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年币厕,在試婚紗的時候發(fā)現(xiàn)自己被綠了列另。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡旦装,死狀恐怖页衙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情同辣,我是刑警寧澤拷姿,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布惭载,位于F島的核電站,受9級特大地震影響响巢,放射性物質(zhì)發(fā)生泄漏描滔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一踪古、第九天 我趴在偏房一處隱蔽的房頂上張望含长。 院中可真熱鬧,春花似錦伏穆、人聲如沸拘泞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陪腌。三九已至,卻和暖如春烟瞧,著一層夾襖步出監(jiān)牢的瞬間诗鸭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工参滴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留强岸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓砾赔,卻偏偏與公主長得像蝌箍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暴心,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

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

  • 版本記錄 前言 OpenGL ES是一個強大的圖形庫妓盲,是跨平臺的圖形API,屬于OpenGL的一個簡化版本专普。iOS...
    刀客傳奇閱讀 7,617評論 0 2
  • 版本記錄 前言 GPUImage是直接利用顯卡實現(xiàn)視頻或者圖像處理的技術(shù)本橙。 作者 先看一下GPUImage 下面給...
    刀客傳奇閱讀 3,723評論 1 15
  • 總感覺 自己就是一只雄鷹 總感覺 振翅就能飛到山頂 總感覺 自己的故事會有人聽 那時候 我們年輕 總感覺 耳畔有你...
    East華閱讀 147評論 0 0
  • 一個人坐客車公交車的時候肯定有人會覺得自己倚在車窗上看向窗外的時候一定很憂郁很好看甚亭。就像那會兒郭敬明45度仰望天空...
    人彡閱讀 220評論 1 1
  • 有多少事我們想做卻不能做,因為生活击胜,因為我們必須保持現(xiàn)狀的活著亏狰。 25那年你辭去了穩(wěn)定每月4500的白領(lǐng)工作,從那...
    舟海魚閱讀 308評論 0 4