GPUImage源碼閱讀(一)

概述

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

基礎(chǔ)

閱讀GPUImage源碼需要一定的知識(shí)儲(chǔ)備,在這里我列出了需要的一些基礎(chǔ)知識(shí):OpenGL ES 2.0、AVFoundationCoreGraphics。如果對(duì)以上框架不熟悉,可以學(xué)習(xí)一下相關(guān)的知識(shí)。在之前我寫(xiě)了一些 OpenGL專(zhuān)題 http://www.itdecent.cn/c/30e2e76bc140 相關(guān)的專(zhuān)題系列,大家也可以去學(xué)習(xí)下。

GLProgram

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

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

初始化的過(guò)程包含了頂點(diǎn)片源著色器的創(chuàng)建、編譯,著色器程序的創(chuàng)建,頂點(diǎn)片源著色器附著到著色器程序等過(guò)程。

- (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();
        
        // 編譯頂點(diǎn)著色器
        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");
        }
        
        // 將頂點(diǎn)片源著色器附著到著色器程序
        glAttachShader(program, vertShader);
        glAttachShader(program, fragShader);
    }
    
    return self;
}
  • 鏈接程序,和編譯型語(yǔ)言一樣,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)識(shí)
    self.initialized = YES;

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

    return YES;
}
  • 使用著色器程序
- (void)use
{
    glUseProgram(program);
}
  • 在給著色器傳值的時(shí)候,我們需要獲取相關(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)的時(shí)候,將著色器等相關(guān)資源清理。
- (void)dealloc
{
    if (vertShader)
        glDeleteShader(vertShader);
        
    if (fragShader)
        glDeleteShader(fragShader);
    
    if (program)
        glDeleteProgram(program);
       
}

GPUImageContext

GPUImageContext類(lèi),提供OpenGL ES基本上下文,GPUImage相關(guān)處理線(xiàn)程,GLProgram緩存、幀緩存。由于是上下文對(duì)象,因此該模塊提供的更多是存取、設(shè)置相關(guān)的方法。

  • 屬性列表
// GPUImage處理OpenGL繪制的相關(guān)隊(duì)列,串行隊(duì)列
@property(readonly, nonatomic) dispatch_queue_t contextQueue;
// 當(dāng)前使用的著色器程序
@property(readwrite, retain, nonatomic) GLProgram *currentShaderProgram;
// OpenGLES上下文對(duì)象
@property(readonly, retain, nonatomic) EAGLContext *context;
// CoreVideo中的紋理緩存
@property(readonly) CVOpenGLESTextureCacheRef coreVideoTextureCache;
// 幀緩存
@property(readonly) GPUImageFramebufferCache *framebufferCache;
  • 初始化過(guò)程。在初始化的過(guò)程中通過(guò)dispatch_queue_set_specific設(shè)置隊(duì)列標(biāo)識(shí),這樣做的原因在Effective Objective-C 2.0 中有提到,或者參見(jiàn)我之前的博客 重拾Effective Objective-C 2.0熱點(diǎn)問(wèn)題
- (id)init;
{
    if (!(self = [super init]))
    {
        return nil;
    }
    
    // 創(chuàng)建OpenGL渲染隊(duì)列
    openGLESContextQueueKey = &openGLESContextQueueKey;
    _contextQueue = dispatch_queue_create("com.sunsetlakesoftware.GPUImage.openGLESContextQueue", GPUImageDefaultQueueAttribute());
    
#if OS_OBJECT_USE_OBJC
    // 設(shè)置隊(duì)列標(biāo)識(shí)
    dispatch_queue_set_specific(_contextQueue, openGLESContextQueueKey, (__bridge void *)self, NULL);
#endif
    // 初始化著色器緩存相關(guān)數(shù)組
    shaderProgramCache = [[NSMutableDictionary alloc] init];
    shaderProgramUsageHistory = [[NSMutableArray alloc] init];
    
    return self;
}
  • 方法列表
// 獲取隊(duì)列標(biāo)識(shí)
+ (void *)contextKey;
// 單例對(duì)象
+ (GPUImageContext *)sharedImageProcessingContext;
// 獲取處理隊(duì)列
+ (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)整,保證紋理不超過(guò)OpenGLES支持最大的尺寸
+ (CGSize)sizeThatFitsWithinATextureForSize:(CGSize)inputSize;
// 將渲染緩存呈現(xiàn)在設(shè)備上
- (void)presentBufferForDisplay;
// 創(chuàng)建GLProgram,首先在緩存中查找,如果沒(méi)有則創(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)于一個(gè)上下文對(duì)象,主要是管理其它的對(duì)象,因此該類(lèi)沒(méi)有太多復(fù)雜的業(yè)務(wù),這里主要看一下幾個(gè)方法:

  • 調(diào)整紋理大小,保證紋理不超過(guò)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,首先在緩存中查找,如果沒(méi)有則創(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上下文對(duì)象,使用的是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)前的上下文對(duì)象,以及當(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)建、編譯、鏈接、使用等過(guò)程。
GPUImageContext 是GPUImage的上下文對(duì)象,管理著OpenGLES上下文對(duì)象,管理著GL程序,管理著幀緩存等GPUImage中的基本組件。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 版本記錄 前言 OpenGL ES是一個(gè)強(qiáng)大的圖形庫(kù),是跨平臺(tái)的圖形API,屬于OpenGL的一個(gè)簡(jiǎn)化版本。iOS...
    刀客傳奇閱讀 8,175評(píng)論 0 2
  • 版本記錄 前言 GPUImage是直接利用顯卡實(shí)現(xiàn)視頻或者圖像處理的技術(shù)。 作者 先看一下GPUImage 下面給...
    刀客傳奇閱讀 3,873評(píng)論 1 15
  • 總感覺(jué) 自己就是一只雄鷹 總感覺(jué) 振翅就能飛到山頂 總感覺(jué) 自己的故事會(huì)有人聽(tīng) 那時(shí)候 我們年輕 總感覺(jué) 耳畔有你...
    East華閱讀 182評(píng)論 0 0
  • 一個(gè)人坐客車(chē)公交車(chē)的時(shí)候肯定有人會(huì)覺(jué)得自己倚在車(chē)窗上看向窗外的時(shí)候一定很憂(yōu)郁很好看。就像那會(huì)兒郭敬明45度仰望天空...
    人彡閱讀 279評(píng)論 1 1
  • 有多少事我們想做卻不能做,因?yàn)樯?,因?yàn)槲覀儽仨毐3脂F(xiàn)狀的活著。 25那年你辭去了穩(wěn)定每月4500的白領(lǐng)工作,從那...
    舟海魚(yú)閱讀 355評(píng)論 0 4

友情鏈接更多精彩內(nèi)容