理解本篇内容需掌握一点OpenGL基础:
1.glProgram的编译,链接,使用。
2.glFramebuffer与glTexture的使用。
简述
GPUImage本质上是一个OpenGL管理框架,其不仅包含大量的滤镜代码,也包含一套使用方便的链式结构,本篇将主要分析滤镜链的原理。
如果将这个链式结构比喻为一个工厂中的流水线,那么这个流水线的结构就会如下所示:
1.总资源管理者
GPUImageContext
2.搭载原料的容器
GPUImageFramebuffer
3.原料提供者
GPUImagePicture,GPUImageMovie...
4.原料加工者
GPUImageFilter
5.成品展示者
GPUImageView
6.原料容器数量的控制者
GPUImageFramebufferCache
下面将依次介绍这些部分在滤镜链中的详细作用
GPUImageContext
作为总资源管理者,以单例方式应用,主要负责:
1.线程管理
2.context
3.framebuffer缓存管理
4.gl信息查询
GPUImageFramebuffer
作为原料的容器,也是滤镜链传递的核心。但之所以称之为容器,是因为每一级所需要的真正材料不是GPUImageFramebuffer,而是里面的glTexture。
GPUImageFramebuffer包含两种类型
1.onlyTexture:只生成了glTexture而没有生成glFramebuffer,多用于第一级输入端,例如GPUImagePicture。
2.texture和framebuffer:生成了glTexture,也生成了glFramebuffer,并使用 glFramebufferTexture2D 将二者绑定,用于滤镜的渲染中,如GPUImageFilter。
GPUImageFramebuffer传递的核心原理:
当前滤镜在渲染时,先绑定到自己的glFramebuffer,然后获取上一级滤镜GPUImageFramebuffer中的glTexture作为输入纹理,通过每个滤镜特有的glProgram,对当前滤镜的输出glTexture进行渲染
GPUImagePicture,GPUImageMovie...
作为原料提供者,处于滤镜链的头部,通过继承GPUImageOutput来实现GPUImageFramebuffer的输出。本质上是将数据转化为OpenGL识别的纹理。
GPUImageFilter
作为原料加工者,处于滤镜链的中段,既继承了GPUImageOutput,又实现了GPUImageInput协议。(无论是Output还是Input都是对于GPUImageFramebuffer而言)
这样便形成了以GPUImageFramebuffer为传递者的链式结构。
而且同一个滤镜可以存在于多条滤镜链中。
以下代码展示的是一个简单的滤镜链,通过效果可以看出每一个filter处理纹理图片的过程:
[picture addTarget:filter1];
[filter1 addTarget:filter2];
[filter1 addTarget:imageView1];
[filter2 addTarget:imageView2];
[filter2 addTarget:filter3];
[filter3 addTarget:imageView3];
[picture processImage];
GPUImageView
作为成品展示者,处于滤镜链的最末端,只接受纹理,不再进行输出。
实现GPUImageInput协议,只接受上级滤镜传来的纹理,而用于展示的glFramebuffer不再使用GPUImageFramebuffer,而直接由自己生成绑定于glRenderbuffer的glFramebuffer。
GPUImageFramebufferCache
作为原料容器的管理者,GPUImageFramebufferCache使GPUImageFramebuffer得以复用,目的是提升性能,就像没必要给每道菜都使用新的盘子一样。
缓存步骤:
1.区分缓存类型
保证同一类型的缓存可以复用,具体就是根据不同的GPUImageFramebuffer生成不同的key,主要以纹理配置,纹理尺寸,以及是否只包含texture作为分类标准,细节如下
- (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];
}
}
2.引用计数
GPUImageFramebufferCache使用缓存的方式与普通的缓存不太一样。
普通缓存:
1.按key找缓存对象。
2.有则返回匹配对象。
3.没有则创建新的对象且存入缓存,并返回新对象。
GPUImageFramebufferCache:
1.按key找缓存对象。
2.如果有,则从缓存中移除当前匹配对象,对象引用计数+1。
3.如果没有,则新建对象,这时并不会把新对象存入缓存,只是对新对象引用计数+1。
这种缓存有点类似于ARC机制,将要渲染时,则对当前GPUImageFramebuffer调用lock方法,使其引用计数+1,渲染完成或滤镜销毁等释放操作后,调用unlock方法,使其引用计数-1,当引用计数为0时,才将对象放入缓存当中。
使用这种缓存的原因
当一个GPUImageFramebuffer引用计数大于0时,不会再将它分配给其他滤镜进行渲染,避免glFramebuffer数据被篡改。
而当一个GPUImageFramebuffer引用计数为0时,才会继续将它分配给其他滤镜。
由此可以得知,当从缓存中请求GPUImageFramebuffer对象时,同类型的GPUImageFramebuffer对象,可能因为引用计数大于0,而不在缓存中。这时会再次生成这个类型的缓存对象。当这几个同类型的对象引用计数都归0时,便都会存入缓存。
所以GPUImageFramebufferCache在请求缓存时,会先使用while循环来清空多余同类缓存,细节如下:
// Something found, pull the old framebuffer and decrement the count
NSInteger currentTextureID = (numberOfMatchingTextures - 1);
while ((framebufferFromCache == nil) && (currentTextureID >= 0))
{
NSString *textureHash = [NSString stringWithFormat:@"%@-%ld", lookupHash, (long)currentTextureID];
framebufferFromCache = [framebufferCache objectForKey:textureHash];
// Test the values in the cache first, to see if they got invalidated behind our back
if (framebufferFromCache != nil)
{
// Withdraw this from the cache while it's in use
[framebufferCache removeObjectForKey:textureHash];
}
currentTextureID--;
}