概述
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.0、AVFoundation令宿、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