上一篇文章中,介紹了整個(gè)GPUImage的整體框架和渲染流程。在整個(gè)渲染流程中,GPUImageFrameBuffer作為一個(gè)紐帶,將各個(gè)不同的元素串聯(lián)起來(lái);同時(shí),還作為OpenGL ES的渲染媒介,實(shí)現(xiàn)了渲染的功能。因此在本文中主要介紹一下GPUImageFrameBuffer以及GPUImageFrameBufferCache。
OpenGL ES FrameBuffer
OpenGL ES的FrameBuffer是渲染發(fā)生的地方,普通的2D圖形的渲染默認(rèn)發(fā)生在屏幕上;而三維的圖形渲染則除了包括像素點(diǎn)的顏色,還有Depth Buffer,Stencil Buffer等其他空間。因此,F(xiàn)rameBuffer就是一個(gè)這些Buffer的一個(gè)集合。
默認(rèn)情況下,F(xiàn)rameBuffer存在于現(xiàn)存中,但是當(dāng)需要進(jìn)行多次渲染或者離屏渲染的時(shí)候,可以通過(guò)創(chuàng)建一個(gè)離屏的FrameBuffer進(jìn)行渲染。當(dāng)需要渲染3D效果時(shí),除了創(chuàng)建frameBuffer以外,還需要?jiǎng)?chuàng)建相應(yīng)的Render Buffer, Depth Buffer, Stencil Buffer,并且將它們attach到frameBuffer上;而當(dāng)只需要渲染2D圖形時(shí),可以直接生成一個(gè)Texture,并且將FrameBuffer渲染的結(jié)果放入Texture中。
生成一個(gè)FrameBuffer的代碼:
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
由于存在離屏渲染的情況,實(shí)際渲染開(kāi)始之前,需要先激活目標(biāo)FrameBuffer,而在渲染完成之后,需要將FrameBuffer綁定為0,回到默認(rèn)的FrameBuffer,才能夠顯示在屏幕上:
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
//Rendering Code
glBindFramebuffer(GL_FRAMEBUFFER, 0);
如果要渲染一個(gè)2D圖像到一個(gè)Texture上,則還需要先生成一個(gè)Texture,并且將Texture綁定到FrameBuffer上。當(dāng)使用FrameBuffer進(jìn)行流的傳遞的時(shí)候,則可以使用這個(gè)Texture:
glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &_texture);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// This is necessary for non-power-of-two textures
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)_size.width, (int)_size.height, 0,GL_RGBA, GL_UNSIGNED_BYTE, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
GPUImageFrameBuffer
GPUImageFrameBuffer不只是簡(jiǎn)單的對(duì)OpenGL ES的FrameBuffer的對(duì)象化封裝,而是一個(gè)對(duì)渲染對(duì)象的對(duì)象化封裝。它生成的可以是一個(gè)帶有Texture作為Attachment的FrameBuffer,也可以僅僅生成一個(gè)Texture作為渲染對(duì)象。它的主要功能有:
- 生成渲染對(duì)象,F(xiàn)rameBuffer或者Texture,帶的參數(shù)包括size和texture option,這兩個(gè)參數(shù)主要是用于FrameBuffer的重用,之后會(huì)介紹到:
- (id)initWithSize:(CGSize)framebufferSize;
- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture;
- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture;
- FrameBuffer的管理,在渲染的時(shí)候激活frameBuffer:
- (void)activateFramebuffer;
- FrameBuffer的引用計(jì)數(shù)。同樣用于FrameBuffer的重用,接下來(lái)會(huì)詳細(xì)介紹:
- (void)lock;
- (void)unlock;
- (void)clearAllLocks;
- (void)disableReferenceCounting;
- (void)enableReferenceCounting;
- 從FrameBuffer中生成圖片或者源數(shù)據(jù):
- (CGImageRef)newCGImageFromFramebufferContents;
- (GLubyte *)byteBuffer;
GPUImageFrameBuffer的引用計(jì)數(shù)
由于GPUImageFrameBuffer需要重用,因此,當(dāng)FrameBuffer的引用為0的時(shí)候,這個(gè)FrameBuffer就會(huì)被放到Cache中,來(lái)給其他的需要的地方進(jìn)行使用。因此作者給GPUImageFrameBuffer引入了引用計(jì)數(shù)的概念。
引用計(jì)數(shù)的概念其實(shí)和OC自帶的MRC有點(diǎn)類(lèi)似,每當(dāng)一個(gè)地方需要使用這個(gè)FrameBuffer的時(shí)候,手動(dòng)調(diào)用lock方法,使引用計(jì)數(shù)+1;當(dāng)渲染完畢或者這個(gè)FrameBuffer使用完畢的時(shí)候,手動(dòng)調(diào)用unlock方法,使引用計(jì)數(shù)-1;當(dāng)引用計(jì)數(shù)為0的時(shí)候,F(xiàn)rameBuffer會(huì)被歸還到Cache中。
調(diào)用增加FrameBuffer引用計(jì)數(shù)方法的地方:
- 每次從Cache中獲取到一個(gè)FrameBuffer的時(shí)候,都會(huì)調(diào)用一次lock方法,因?yàn)榧热猾@取了FrameBuffer就是需要使用它進(jìn)行渲染的時(shí)候,因此這個(gè)是最合適的調(diào)用時(shí)機(jī);
- 將一個(gè)FrameBuffer作為Output傳給下一個(gè)Input的時(shí)候,會(huì)調(diào)用
setInputFrameBuffer:atIndex:方法。因?yàn)檫@個(gè)FrameBuffer會(huì)被傳遞給下一個(gè)input進(jìn)行使用,因此我們需要手動(dòng)的將這個(gè)FrameBuffer的引用計(jì)數(shù)+1; - 如果需要將一個(gè)Filter的輸出進(jìn)行截圖的話(
usingNextFrameForImageCapture == YES),也需要手動(dòng)調(diào)用lock方法。因?yàn)槟J(rèn)情況下,當(dāng)下一個(gè)input渲染完成之后,就會(huì)釋放這個(gè)FrameBuffer。如果你需要對(duì)當(dāng)前的Filter的輸出進(jìn)行截圖的話,則需要保留住這個(gè)FrameBuffer。
調(diào)用減少FrameBuffer引用計(jì)數(shù)方法的地方:
- 在
renderToTextureWithVertices:textureCoordinates:渲染完成之后,需要將從上一個(gè)Output中傳過(guò)來(lái)的所有InputFrameBuffer都unlock掉; - 在informTargetsAboutNewFrameAtTime:通知下一個(gè)input之后,需要將當(dāng)前的這個(gè)Output中的outputFrameBuffer給unlock掉;
不需要引用計(jì)數(shù)的情況:
在有些地方我們是不需要使用引用計(jì)數(shù)的,比如GPUImagePicture。因?yàn)樯闪薖icture之后,它只會(huì)有一個(gè)outputFrameBuffer。而且這個(gè)frameBuffer只有在GPUImagePicture初始化的時(shí)候生成。因此這個(gè)FrameBuffer不能被釋放,否則以后它的target就不能使用了。因此GPUImagePicture初始化的時(shí)候調(diào)用了disableReferenceCounting方法。只有當(dāng)這個(gè)GPUImagePicture在dealloc的時(shí)候才能被釋放。
GPUImageFrameBufferCache
由于GPUImage添加濾鏡可以形成一個(gè)FilterChain,因此,在渲染的過(guò)程中,可能會(huì)需要很多個(gè)FrameBuffer,但是正如上文所說(shuō),每生成一個(gè)FrameBuffer都需要占用一定的內(nèi)存或者顯存。因此,必須保證盡可能少創(chuàng)建FrameBuffer。而GPUImageFrameBufferCache就是用來(lái)管理所有的FrameBuffer的。
根據(jù)上面對(duì)GPUImageFrameBuffer的介紹,每個(gè)FrameBuffer其實(shí)就是一塊內(nèi)存或者緩存,因此只要它們的size和textureOption是一樣的,那么這個(gè)FrameBuffer就是完全可以重用的。重用的過(guò)程如下:
- 首先就是要使用size和textureOptions生成一個(gè)Key:
- (NSString *)hashForSize:(CGSize)size textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture;
{
if (onlyTexture)
{
return [NSString stringWithFormat:@"%.1fx%.1f-%d:%d:%d:%d:%d:%d:%d-NOFB", size.width, size.height, textureOptions.minFilter, textureOptions.magFilter, textureOptions.wrapS, textureOptions.wrapT, textureOptions.internalFormat, textureOptions.format, textureOptions.type];
}
else
{
return [NSString stringWithFormat:@"%.1fx%.1f-%d:%d:%d:%d:%d:%d:%d", size.width, size.height, textureOptions.minFilter, textureOptions.magFilter, textureOptions.wrapS, textureOptions.wrapT, textureOptions.internalFormat, textureOptions.format, textureOptions.type];
}
}
- 第二步是根據(jù)這個(gè)生成的key,查詢?cè)赾ache里面有多少個(gè)滿足這個(gè)條件的FrameBuffer可用。在
GPUImageFrameBufferCache中,包含了兩個(gè)Dictionary:
NSMutableDictionary *framebufferCache;
NSMutableDictionary *framebufferTypeCounts;
其中framebufferTypeCounts是保存了滿足當(dāng)前size和textureOptions生成的key的FrameBuffer個(gè)數(shù),key就是上面生成的hashKey;而framebufferCache則是保存的每個(gè)Texture對(duì)象,key是上面生成的hashKey+“-i”;比如滿足當(dāng)前size和textureOptions的FrameBuffer有5個(gè),則在framebufferCache里面會(huì)有haskey-0~hashkey-4這些key和對(duì)應(yīng)的FrameBuffer。
因此,查詢的過(guò)程是:
- 使用HashKey查詢到滿足條件的FrameBuffer個(gè)數(shù):
NSString *lookupHash = [self hashForSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
NSNumber *numberOfMatchingTexturesInCache = [framebufferTypeCounts objectForKey:lookupHash];
NSInteger numberOfMatchingTextures = [numberOfMatchingTexturesInCache integerValue];
- 如果個(gè)數(shù)為零,則生成一個(gè)新的FrameBuffer并且返回:
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
- 如果有滿足條件的FrameBuffer,則獲取index最大的一個(gè)Key對(duì)應(yīng)的FrameBuffer,并且分別更新兩個(gè)FrameBuffer對(duì)應(yīng)的Key和Value:
NSInteger currentTextureID = (numberOfMatchingTextures - 1);
while ((framebufferFromCache == nil) && (currentTextureID >= 0))
{
NSString *textureHash = [NSString stringWithFormat:@"%@-%ld", lookupHash, (long)currentTextureID];
framebufferFromCache = [framebufferCache objectForKey:textureHash];
if (framebufferFromCache != nil) {
[framebufferCache removeObjectForKey:textureHash];
}
currentTextureID--;
}
currentTextureID++;
[framebufferTypeCounts setObject:[NSNumber numberWithInteger:currentTextureID] forKey:lookupHash];
if (framebufferFromCache == nil) {
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
}
- 在返回FrameBuffer之前,需要將FrameBuffer進(jìn)行一次lock,增加引用計(jì)數(shù)。
- 當(dāng)一個(gè)FrameBuffer的引用計(jì)數(shù)為0的時(shí)候,我們就會(huì)將這個(gè)FrameBuffer重新放置到Cache中以便重用。
GPUImageFrameBufferCache可以創(chuàng)建多個(gè),一般每一個(gè)GPUImageContext中會(huì)有一個(gè)公用的GPUImageFrameBufferCache。通過(guò)這個(gè)Cache可以獲得對(duì)應(yīng)的GPUImageContext中得到對(duì)應(yīng)的FrameBuffer對(duì)象。