GPUImage源碼解析 -- GPUImageFrameBuffer

上一篇文章中,介紹了整個(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ù)方法的地方:

  1. 每次從Cache中獲取到一個(gè)FrameBuffer的時(shí)候,都會(huì)調(diào)用一次lock方法,因?yàn)榧热猾@取了FrameBuffer就是需要使用它進(jìn)行渲染的時(shí)候,因此這個(gè)是最合適的調(diào)用時(shí)機(jī);
  2. 將一個(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;
  3. 如果需要將一個(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ù)方法的地方:

  1. renderToTextureWithVertices:textureCoordinates:渲染完成之后,需要將從上一個(gè)Output中傳過(guò)來(lái)的所有InputFrameBuffer都unlock掉;
  2. 在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)存或者緩存,因此只要它們的sizetextureOption是一樣的,那么這個(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)前sizetextureOptions生成的key的FrameBuffer個(gè)數(shù),key就是上面生成的hashKey;而framebufferCache則是保存的每個(gè)Texture對(duì)象,key是上面生成的hashKey+“-i”;比如滿足當(dāng)前sizetextureOptions的FrameBuffer有5個(gè),則在framebufferCache里面會(huì)有haskey-0~hashkey-4這些key和對(duì)應(yīng)的FrameBuffer。

因此,查詢的過(guò)程是:

  1. 使用HashKey查詢到滿足條件的FrameBuffer個(gè)數(shù):
NSString *lookupHash = [self hashForSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
NSNumber *numberOfMatchingTexturesInCache = [framebufferTypeCounts objectForKey:lookupHash];
NSInteger numberOfMatchingTextures = [numberOfMatchingTexturesInCache integerValue];
  1. 如果個(gè)數(shù)為零,則生成一個(gè)新的FrameBuffer并且返回:
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
  1. 如果有滿足條件的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];
 }
  1. 在返回FrameBuffer之前,需要將FrameBuffer進(jìn)行一次lock,增加引用計(jì)數(shù)。
  2. 當(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ì)象。

?著作權(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)容

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