GPUImage(四):你們處理的都是GPUImageFramebuffer類型

GPUImage作為iOS相當(dāng)老牌的圖片處理三方庫已經(jīng)有些日子了(2013年發(fā)布第一個(gè)版本),至今甚至感覺要離我們慢慢遠(yuǎn)去(2015年更新了最后一個(gè)release)??赡墁F(xiàn)在分享這個(gè)稍微有點(diǎn)晚,再加上落影大神早已發(fā)布過此類文章,但是還是想從自己的角度來分享一下對其的理解和想法。

本文集所有內(nèi)容皆為原創(chuàng),嚴(yán)禁轉(zhuǎn)載。


? ? 在使用GPUImage處理圖片或者視頻時(shí),并不能直接對iOS官方定義的的UIImage、CGImage、CIImage進(jìn)行操作。那么如果要使用GPUImage的話,第一步操作就是把你想處理的數(shù)據(jù)類型用一個(gè)GPUImage定義的載體承載,才能在GPUImage的處理鏈中進(jìn)行一步一步的操作,這個(gè)載體就是GPUImageFramebuffer。我們把一張圖像的像素等攜帶的全部信息看成好幾種散裝液體顏料的集合,系統(tǒng)提供了UIImage(最常用的圖像類型)相當(dāng)于一個(gè)顏料盒子,想把圖片顯示在iPhone設(shè)備屏幕上的話首先就是要把散裝顏料裝到這個(gè)顏料盒子里。GPUImageFramebuffer就是GPUImage中的顏料盒子,所以要先把UIImage盒子中的顏料倒到GPUImageFramebuffer中,才可以用GPUImage對這些顏料進(jìn)行再次調(diào)色。調(diào)色的過程之前的文章中比喻成了一個(gè)多維度的管道,因此,在處理過程中就像GPUImageFramebuffer帶著顏料在管子里流動(dòng),經(jīng)過一個(gè)節(jié)點(diǎn)處理完后會(huì)有一個(gè)新盒子把調(diào)好色的顏料接住再往下流動(dòng)。

GPUImageFramebuffer

@property(readonly) CGSize size; ?//顏料盒子的大小,盒子創(chuàng)建的時(shí)候你要知道需要用一個(gè)多大的盒子才能剛好容納這些顏料

@property(readonly) GPUTextureOptions textureOptions; ?//用于創(chuàng)建紋理時(shí)的相關(guān)設(shè)置

@property(readonly) GLuint texture; //紋理對象指針

@property(readonly) BOOL missingFramebuffer; //這個(gè)屬性的設(shè)置就涉及到framebuffer和texture的關(guān)系,此處先不細(xì)說。GPUImage中將texture作為framebuffer對象的一個(gè)屬性實(shí)現(xiàn)兩者關(guān)系的綁定。若missingFramebuffer為YES,則對象只會(huì)生成texture,例如GPUImagePicture對象的圖像數(shù)據(jù)就不需要用到framebuffer,只要texture即可;若為NO,則會(huì)先生成framebuffer對象,再生成texture對象,并進(jìn)行綁定。

- (id)initWithSize:(CGSize)framebufferSize; //設(shè)置buffer大小初始化,texture設(shè)置為默認(rèn),又創(chuàng)建framebuffer又創(chuàng)建texture對象。

- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture;

- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture;? //自己創(chuàng)建好texture替代GPUImageFramebuffer對象初始化時(shí)創(chuàng)建的texture

- (void)activateFramebuffer; //綁定frame buffer object才算是創(chuàng)建完成,也就是FBO在使用前,一定要調(diào)用此方法。

- (void)lock;?

- (void)unlock;?

- (void)clearAllLocks;

- (void)disableReferenceCounting;

- (void)enableReferenceCounting; //以上方法涉及framebuffer對象的內(nèi)存管理,之后會(huì)具體說明。開發(fā)時(shí)基本不會(huì)手動(dòng)調(diào)用以上方法。

- (CGImageRef)newCGImageFromFramebufferContents; ?//從framebuffer中導(dǎo)出生成CGImage格式圖片數(shù)據(jù)

- (void)restoreRenderTarget;?

- (void)lockForReading;

- (void)unlockAfterReading; //以上方法涉及到GPUImageFramebuffer對象管理自身生成的用于存儲(chǔ)處理后的圖像數(shù)據(jù)CVPixelBufferRef對象。

- (NSUInteger)bytesPerRow; //返回CVPixelBufferRef類型對象實(shí)際占用內(nèi)存大小

- (GLubyte *)byteBuffer; //返回CVPixelBufferRef類型對象

從以上GPUImageFramebuffer.h的內(nèi)容可看到,主要內(nèi)容分為三大部分:1.framebuffer及texture的創(chuàng)建。2.GPUImage中GPUImageFramebuffer類型對象的內(nèi)存管理。3.實(shí)際操作過程中數(shù)據(jù)存儲(chǔ)的CVPixelBufferRef類型對象的具體操作。接下來就來看一下這三點(diǎn)中涉及到的具體類型到底是個(gè)啥以及在GPUImage框架中做了哪些事。

1.framebuffer及texture

·texture

百度百科中對紋理的解釋:

一般說來,紋理是表示物體表面的一幅或幾幅二維圖形,也稱紋理貼圖(texture)。當(dāng)把紋理按照特定的方式映射到物體表面上的時(shí)候,能使物體看上去更加真實(shí)。當(dāng)前流行的圖形系統(tǒng)中,紋理繪制已經(jīng)成為一種必不可少的渲染方法。在理解紋理映射時(shí),可以將紋理看做應(yīng)用在物體表面的像素顏色。在真實(shí)世界中,紋理表示一個(gè)對象的顏色、圖案以及觸覺特征。紋理只表示對象表面的彩色圖案,它不能改變對象的幾何形式。更進(jìn)一步的說,它只是一種高強(qiáng)度的計(jì)算行為。

在大學(xué)時(shí)期有一門計(jì)算機(jī)圖形課,主要是在Windows上使用C進(jìn)行OpenGL開發(fā)。當(dāng)時(shí)我做了一架直升飛機(jī),雖然具體如何開發(fā)OpenGL現(xiàn)在已經(jīng)有點(diǎn)陌生,但是其中印象非常深刻的有兩個(gè)地方:1.只能畫三角形,正方形是通過兩個(gè)三角形組成的,圓形是有非常多個(gè)三角形組成的,三角形越多,鋸齒越不明顯。2.畫好各種形狀組成三維圖形后可以往圖形上面貼圖,就好像罐頭的包裝一樣,剛做好的罐頭其實(shí)只是一個(gè)鋁制或者其他材料制成的沒有任何圖案的圓柱體,出產(chǎn)前最后一道工序就是給罐頭貼上一圈紙或者噴上相應(yīng)的圖案。最后出廠運(yùn)往各個(gè)賣場,我才能買到印有“某巢”以及各個(gè)信息在罐子上的奶粉。紋理就是貼在上面的紙或者印在上面的圖案。

GPUImageFramebuffer中通過調(diào)用下面的方法創(chuàng)建texture,具體實(shí)現(xiàn)其實(shí)和OpenGL ES一模一樣。

- (void)generateTexture;

{

glActiveTexture(GL_TEXTURE1);? //紋理單元相當(dāng)于顯卡中存放紋理的格子,格子有多少取決于顯卡貴不貴。此方法并不是激活紋理單元,而是選擇當(dāng)前活躍的紋理單元。

glGenTextures(1, &_texture); //生成紋理,第二個(gè)參數(shù)為texture的地址,生成紋理后texture就指向紋理所在的內(nèi)存區(qū)域。

glBindTexture(GL_TEXTURE_2D, _texture); ?//將上方創(chuàng)建的紋理名稱與活躍的紋理單元綁定,個(gè)人理解為暫時(shí)的紋理單元命名。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, _textureOptions.minFilter);//當(dāng)所顯示的紋理比加載進(jìn)來的紋理小時(shí),采用GL_LINEAR的方法來處理

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, _textureOptions.magFilter);//當(dāng)所顯示的紋理比加載進(jìn)來的紋理大時(shí),采用GL_LINEAR的方法來處理

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, _textureOptions.wrapS);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT);

//以上配置texture的參數(shù)都存放在GPUTextureOptions結(jié)構(gòu)體中,使用時(shí)候如果有特殊要求,也可以自己創(chuàng)建然后通過初始化方法傳入。

}

注:以上源碼中最后兩行的注釋

// This is necessary for non-power-of-two textures

Non-Power-of-Two Textures譯為無二次冪限制的紋理。大概意思是紋理大小可以不等于某個(gè)數(shù)的二次冪。

紋理的WRAP設(shè)置解釋如下(截圖內(nèi)容來自:http://blog.csdn.net/wangdingqiaoit/article/details/51457675)


·framebuffer

字面上的意思是幀緩存,在OpenGL中,幀緩存的實(shí)例被叫做FBO。個(gè)人理解:如果texture是罐頭上的內(nèi)容,那framebuffer就是那張裹在罐頭上的紙,它負(fù)責(zé)對紋理內(nèi)容進(jìn)行緩存并渲染到屏幕上,這個(gè)過程就叫做render to texture。當(dāng)然framebuffer中不僅可以緩存原始的紋理內(nèi)容,還可以是經(jīng)過OpenGL處理后的內(nèi)容。比如照片中牛奶罐頭上除了有本身的名字、圖案和說明這些內(nèi)容,我們看上去還有相應(yīng)的質(zhì)感和反光效果,這個(gè)就可以通過獲取反光中的實(shí)際影像進(jìn)行透明、拉伸、霧化等效果處理后得到與實(shí)際反光一模一樣的圖,最終渲染到牛奶罐上。

從這個(gè)角度理解的話,可以把texture看作是framebuffer的一部分,因此在GPUImage中把指向texture的地址作為GPUImageFramebuffer的一個(gè)屬性。當(dāng)然也有不需要?jiǎng)?chuàng)建frambuffer的情況,比如在初始化輸入源時(shí)例如GPUImagePicture類型對象,載入圖片資源時(shí)只需要?jiǎng)?chuàng)建texture即可,GPUImageFramebuffer的其中一個(gè)初始化方法的其中一個(gè)參數(shù)onlyGenerateTexture為YES時(shí),就可以只創(chuàng)建texture,把圖片信息轉(zhuǎn)化成紋理,而沒有進(jìn)行framebuffer的創(chuàng)建。

glBindTexture(GL_TEXTURE_2D, _texture); //將一個(gè)命名的紋理綁定到一個(gè)紋理目標(biāo)上,當(dāng)把一張紋理綁定到一個(gè)目標(biāo)上時(shí),之前對這個(gè)目標(biāo)的綁定就會(huì)失效。當(dāng)一張紋理被第一次綁定時(shí),它假定成為指定的目標(biāo)類型。例如,一張紋理若第一次被綁定到GL_TEXTURE_1D上,就變成了一張一維紋理;若第一次被綁定到GL_TEXTURE_2D上,就變成了一張二維紋理。當(dāng)使用glBindTexture綁定一張紋理后,它會(huì)一直保持活躍狀態(tài)直到另一張紋理被綁定到同一個(gè)目標(biāo)上,或者這個(gè)被綁定的紋理被刪除了(使用glDeleteTextures)。

函數(shù)具體參數(shù)解釋:http://www.dreamingwish.com/frontui/article/default/glbindtexture.html

glTexImage2D(GL_TEXTURE_2D, 0, _textureOptions.internalFormat, (int)_size.width, (int)_size.height, 0, _textureOptions.format, _textureOptions.type, 0); //用來指定二維紋理和立方體紋理。

函數(shù)具體參數(shù)的解釋:http://blog.csdn.net/csxiaoshui/article/details/27543615

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0); //參數(shù)GL_COLOR_ATTACHMENT0是告訴OpenGLES把紋理對像綁定到FBO的0號(hào)綁定點(diǎn)(一個(gè)FBO在同一個(gè)時(shí)間內(nèi)可以綁定多個(gè)顏色緩沖區(qū),每個(gè)對應(yīng)FBO的一個(gè)綁定點(diǎn)),參數(shù)GL_TEXTURE_2D是指定紋理的格式為二維紋理,_texture保存的是紋理標(biāo)識(shí),指向一個(gè)之前就準(zhǔn)備好了的紋理對像。紋理可以是多重映射的圖像,最后一個(gè)參數(shù)指定級(jí)級(jí)為0,指的是使用原圖像。

glBindTexture(GL_TEXTURE_2D, 0); //這是在創(chuàng)建framebuffer并綁定紋理的最后一步,個(gè)人理解是在綁定之后framebuffer已經(jīng)獲得texture,需要釋放framebuffer對texture的引用

有創(chuàng)建就有銷毀,GPUImageFramebuffer有一個(gè)私有方法- (void)destroyFramebuffer,在dealloc時(shí)調(diào)用。其中主要的操作是:

glDeleteFramebuffers(1, &framebuffer);

framebuffer = 0;


2.GPUImage對GPUImageFramebuffer類型對象的管理


相信很多使用過GPUImage都會(huì)有同樣的經(jīng)歷,都會(huì)遇到這個(gè)斷言。以下就拿具體案例來解釋GPUImage對GPUImageFramebuffer到底做了哪些事情。場景是:創(chuàng)建一個(gè)GPUImagePicture對象pic,一個(gè)GPUImageHueFilter對象filter和一個(gè)GPUImageView對象imageView,實(shí)現(xiàn)效果是pic通過filter處理后的效果顯示在imageView上。具體的處理過程就不過多說明,我們來主要看一下GPUImageFramebuffer在整個(gè)處理過程中發(fā)生的情況。

·GPUImagePicture對象pic中的framebuffer

在對初始化傳入的UIImage對象進(jìn)行一系列處理后,pic進(jìn)行自身的framebuffer的獲取:

可以看到,framebuffer對象并不是通過初始化GPUImageFramebuffer來創(chuàng)建的,而是通過調(diào)用單例[GPUImageContext sharedFramebufferCache]的其中一個(gè)方法得到的。sharedFramebufferCache其實(shí)是一個(gè)普通的NSObject子類對象,并不具有像NSCache對象對數(shù)據(jù)緩存的實(shí)際處理功能,不過GPUImage通過單例持有這個(gè)sharedFramebufferCache對象來對過程中產(chǎn)生的framebuffer進(jìn)行統(tǒng)一管理。獲取framebuffer的方法有兩個(gè)入?yún)ⅲ?.紋理大小,在pic中的這個(gè)大小正常情況下是傳入圖片的初始大小,當(dāng)然還有不正常情況之后再說。2.是否返回只有texture的的framebuffer對象。

第一步:

傳入的紋理大小,textureOptions(默認(rèn))、是否只要texture調(diào)用sharedFramebufferCache的- (NSString *)hashForSize:(CGSize)size textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture;方法得到一個(gè)用于查詢的唯一值lookupHash。由此可見如果紋理大小一致的GPUImagePicture對象獲取到的lookupHash是相同的。

第二步:

lookupHash作為key,在sharedFramebufferCache的一個(gè)字典類型的屬性framebufferTypeCounts獲取到numberOfMatchingTexturesInCache,如字面意思,在緩存中滿足條件的GPUImageFramebuffer類型對象的個(gè)數(shù)。轉(zhuǎn)換為整數(shù)類型為numberOfMatchingTextures

第三步:

如果numberOfMatchingTexturesInCache小于1也就是沒找到的話,就調(diào)用GPUImageFramebuffer的初始化方法創(chuàng)建一個(gè)新的framebuffer對象。否則:將lookupHash和(numberOfMatchingTextures - 1)拼接成key從sharedFramebufferCache的另一個(gè)字典類型的屬性framebufferCache中獲取到framebuffer對象。再更新framebufferTypeCounts中numberOfMatchingTexturesInCache數(shù)值(減1)

第四步:

以防最后返回的framebuffer對象為nil,最后做了判斷如果為nil的話就初始化創(chuàng)建一個(gè)。

第五步:

調(diào)用作為返回值的framebuffer對象的- (void)lock;方法。主要是對framebufferReferenceCount進(jìn)行+1操作。framebufferReferenceCount為framebuffer的一個(gè)屬性,初始化時(shí)候?yàn)?,作用也是字面意思:對象被引用的次數(shù)。此時(shí)需要進(jìn)行+1操作是因?yàn)閒ramebuffer對象即將被獲取它的pic引用并將圖片內(nèi)容載入。

但是!與GPUImageFilter類型對象不同的是在pic獲取到framebuffer后,進(jìn)行了[outputFramebuffer disableReferenceCounting];。這個(gè)方法里將framebuffer的referenceCountingDisabled設(shè)置為YES。而這個(gè)屬性的值在- (void)lock;方法中又會(huì)導(dǎo)致不一樣的結(jié)果。如果referenceCountingDisabled為YES的話將不會(huì)對framebufferReferenceCount進(jìn)行+1操作。

- (void)lock;

{

if (referenceCountingDisabled)

{

return;

}

framebufferReferenceCount++;

}

然而問題就出在這里,在pic獲取到framebuffer之前,從sharedFramebufferCache找到framebuffer后就對它調(diào)用了- (void)lock;方法,此時(shí)pic獲取到的framebuffer對象的framebufferReferenceCount已經(jīng)被+1,而referenceCountingDisabled是在這之后才設(shè)置為YES,從而導(dǎo)致在pic對象dealloc時(shí)候自身的outputFramebuffer屬性并未得到釋放從而引起內(nèi)存泄漏。為了解決這個(gè)問題我寫了一個(gè)GPUImagePicture的caterogy,重寫dealloc方法,將原本的[outputFramebuffer unlock];替換成[outputFramebuffer clearAllLocks];,保證outputFramebuffer的framebufferReferenceCount被重置為0,從而保證outputFramebuffer能順利釋放。

·GPUImageHueFilter對象filter中的framebuffer

我們以結(jié)構(gòu)最簡單的單輸入源濾鏡對象作為例子,通過GPUImageHueFilter對象filter。filter相比pic來說相同點(diǎn)是:自身都會(huì)通過sharedFramebufferCache獲取到一個(gè)framebuffer用來存儲(chǔ)經(jīng)過自身處理后的數(shù)據(jù)并傳遞給下一個(gè)對象。不同點(diǎn)是:filter有一個(gè)firstInputFramebuffer變量,作用是引用上一個(gè)節(jié)點(diǎn)的outputFramebuffer。如果是繼承自GPUImageTwoInputFilter的濾鏡對象來說,它的成員變量將會(huì)多一個(gè)secondInputFramebuffer。若想進(jìn)行到filter這,必須將filter作為pic的target,并調(diào)用pic的processImage方法。filter中方法調(diào)用順序是:

1.對輸入的framebuffer引用并調(diào)用lock方法。假設(shè)pic的framebuffer為初始化創(chuàng)建的,傳入前framebufferReferenceCount為1,經(jīng)過此方法后framebufferReferenceCount則為2

- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex {

firstInputFramebuffer = newInputFramebuffer;

[firstInputFramebuffer lock];

}?

2.filter的處理操作,也就是framebuffer的渲染紋理。這里就復(fù)雜了,那就不涉及到具體的渲染過程了。

- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates

filter的outputFramebuffer與pic一樣,都是通過shareFramebufferCache查找獲得。因此,outputFramebuffer變量被賦值時(shí)framebuffer的framebufferReferenceCount已經(jīng)為1。接下來有一個(gè)判斷條件:usingNextFrameForImageCapture。在類中全局搜一下,發(fā)現(xiàn)在調(diào)用- (void)useNextFrameForImageCapture;方法時(shí)會(huì)將usingNextFrameForImageCapture設(shè)置為YES。那么什么時(shí)候會(huì)調(diào)用這個(gè)方法呢?有用GPUImage寫過最簡單的離屏渲染功能實(shí)現(xiàn)的都會(huì)覺得對這個(gè)方法有點(diǎn)眼熟,那就是在filter處理完后導(dǎo)出圖片前必須要調(diào)用這個(gè)方法。為啥呢?原因就在這個(gè)判斷條件這里,如果usingNextFrameForImageCapture為YES,那么outputFramebuffer要再lock一次,就是為了保證在處理完成后還需要引用outputFramebuffer,從而才可以從中生成圖片對象,否則就被回收進(jìn)shareFramebufferCache啦。

進(jìn)行過一頓操作后,最后會(huì)調(diào)用輸入源的unlock方法。這時(shí)候firstInputFramebuffer的framebufferReferenceCount按照正常情況的話將會(huì)為0,就會(huì)被添加到shareFramebufferCache中。

[firstInputFramebuffer unlock];

3.接下來執(zhí)行的方法中會(huì)把自身的outputFramebuffer傳遞給鏈中的下一個(gè)節(jié)點(diǎn),就像pic到filter的過程一樣。

- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime;

這里的[self framebufferForOutput]在正常的filter中會(huì)返回outputFramebuffer。如果usingNextFrameForImageCapture為YES的話,可以簡單理解當(dāng)前對象的outputFramebuffer傳遞給下一個(gè)target后還有其他用處的話就不置空outputFramebuffer變量。如果usingNextFrameForImageCapture為NO的話,當(dāng)前outputFramebuffer被置為nil,但是原先outputFramebuffer指向的framebuffer并不會(huì)被回收到shareFramebufferCache。原因是:framebuffer已經(jīng)傳遞給下個(gè)target,在相應(yīng)賦值方法中對framebuffer調(diào)用了lock方法。周而復(fù)始,直到最后一個(gè)節(jié)點(diǎn),要么生成圖片,要么顯示。

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

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

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