GPUImage源碼閱讀(一)

概述

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

基礎

閱讀GPUImage源碼需要一定的知識儲備,在這里我列出了需要的一些基礎知識:OpenGL ES 2.0AVFoundationCoreGraphics。如果對以上框架不熟悉,可以學習一下相關的知識。在之前我寫了一些 OpenGL專題 http://www.lxweimin.com/c/30e2e76bc140 相關的專題系列,大家也可以去學習下。

GLProgram

之前在學習OpenGL的時候,我們首先要需要做的就是初始化OpenGL ES環境,編譯、鏈接頂點著色器和片元著色器。在GPUImage中封裝了GLProgram專門處理OpenGL ES程序的創建等相關工作。
GLProgram的方法不是很多,這里簡單介紹:

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

初始化的過程包含了頂點片源著色器的創建、編譯,著色器程序的創建,頂點片源著色器附著到著色器程序等過程。

- (id)initWithVertexShaderString:(NSString *)vShaderString 
            fragmentShaderString:(NSString *)fShaderString;
{
    if ((self = [super init])) 
    {
        _initialized = NO;
        
        // 初始化屬性數組
        attributes = [[NSMutableArray alloc] init];
        // 初始化uniform屬性數組
        uniforms = [[NSMutableArray alloc] init];
        
        // 創建著色器程序
        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);
    
    // 獲取鏈接狀態
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    // 鏈接失敗則返回
    if (status == GL_FALSE)
        return NO;
    
    // 鏈接成功,就可以刪掉相關的shader,釋放資源
    if (vertShader)
    {
        glDeleteShader(vertShader);
        vertShader = 0;
    }
    if (fragShader)
    {
        glDeleteShader(fragShader);
        fragShader = 0;
    }
    
    // 設置初始化成功標識
    self.initialized = YES;

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

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

GPUImageContext

GPUImageContext類,提供OpenGL ES基本上下文,GPUImage相關處理線程,GLProgram緩存、幀緩存。由于是上下文對象,因此該模塊提供的更多是存取、設置相關的方法。

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

由于GPUImageContext相當于一個上下文對象,主要是管理其它的對象,因此該類沒有太多復雜的業務,這里主要看一下幾個方法:

  • 調整紋理大小,保證紋理不超過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;
}
  • 創建GLProgram,首先在緩存中查找,如果沒有則創建
- (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;
}
  • 創建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;
}
  • 設置當前的上下文對象,以及當前的著色器程序。
- (void)setContextShaderProgram:(GLProgram *)shaderProgram;
{
    EAGLContext *imageProcessingContext = [self context];
    if ([EAGLContext currentContext] != imageProcessingContext)
    {
        [EAGLContext setCurrentContext:imageProcessingContext];
    }
    
    if (self.currentShaderProgram != shaderProgram)
    {
        self.currentShaderProgram = shaderProgram;
        [shaderProgram use];
    }
}

總結

GLProgram 與著色器程序創建息息相關,其中包括創建、編譯、鏈接、使用等過程。
GPUImageContext 是GPUImage的上下文對象,管理著OpenGLES上下文對象,管理著GL程序,管理著幀緩存等GPUImage中的基本組件。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

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