简介

由于GPUImage添加滤镜可以形成一个FilterChain,因此,在渲染的过程中,可能会需要很多个FrameBuffer,但是正如上文所说,每生成一个FrameBuffer都需要占用一定的内存或者显存。因此,必须保证尽可能少创建FrameBuffer。而GPUImageFrameBufferCache就是用来管理所有的FrameBuffer的。

根据上面对GPUImageFrameBuffer的介绍,每个FrameBuffer其实就是一块内存或者缓存,因此只要它们的size和textureOption是一样的,那么这个FrameBuffer就是完全可以重用的。

一般来说,GPUImageFrameBufferCache可以创建多个,一般每一个GPUImageContext中会有一个公用的GPUImageFrameBufferCache。通过这个Cache可以获得对应的GPUImageContext中得到对应的FrameBuffer对象。

重用过程如下:

  • 首先就是要使用size和textureOptions生成一个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];}
}
  • 第二步是根据这个生成的key,查询在cache里面有多少个满足这个条件的FrameBuffer可用。在GPUImageFrameBufferCache中,包含了两个Dictionary:
NSMutableDictionary *framebufferCache;
NSMutableDictionary *framebufferTypeCounts;

其中framebufferTypeCounts是保存了满足当前size和textureOptions生成的key的FrameBuffer个数,key就是上面生成的hashKey;而framebufferCache则是保存的每个Texture对象,key是上面生成的hashKey+“-i”;比如满足当前size和textureOptions的FrameBuffer有5个,则在framebufferCache里面会有haskey-0~hashkey-4这些key和对应的FrameBuffer。

因此,查询的过程是:

  1. 使用HashKey查询到满足条件的FrameBuffer个数:
NSString *lookupHash = [self hashForSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
NSNumber *numberOfMatchingTexturesInCache = [framebufferTypeCounts objectForKey:lookupHash];
NSInteger numberOfMatchingTextures = [numberOfMatchingTexturesInCache integerValue];
  1. 如果个数为零,则生成一个新的FrameBuffer并且返回:
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
  1. 如果有满足条件的FrameBuffer,则获取index最大的一个Key对应的FrameBuffer,并且分别更新两个FrameBuffer对应的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进行一次lock,增加引用计数。
  2. 当一个FrameBuffer的引用计数为0的时候,我们就会将这个FrameBuffer重新放置到Cache中以便重用。

思考

我们为什么要用cache里的framebuffer呢?自己创建一个,使用完后再释放行不行呢?

答案显示是NO。

我们来看一下GPUImageFramebuffer类的代码,在dealloc中,调用了destroyFramebuffer方法,这个方法的实现如下。

- (void)destroyFramebuffer;
{runSynchronouslyOnVideoProcessingQueue(^{[GPUImageContext useImageProcessingContext];if (framebuffer){glDeleteFramebuffers(1, &framebuffer);framebuffer = 0;}if ([GPUImageContext supportsFastTextureUpload] && (!_missingFramebuffer)){
#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONEif (renderTarget){CFRelease(renderTarget);renderTarget = NULL;}if (renderTexture){CFRelease(renderTexture);renderTexture = NULL;}
#endif}else{glDeleteTextures(1, &_texture);}});
}

问题就出在其中的renderTarget上,当创建GPUImageFramebuffer时给onlyTexture参数填NO(一般就是填NO的)时,会创建一个CVPixelBufferRef类型的变量renderTarget,当用CFRelease去释放这个变量时,它占用的内存并不会立即释放,而是要调用

CVOpenGLESTextureCacheFlush([[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], 0);

之后,才会真正释放内存。这个现象的原因可以在GPUImageFrameBuffer的init函数中找到。

CVOpenGLESTextureCacheRef coreVideoTextureCache = [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache];// Code originally sourced from http://allmybrain.com/2011/12/08/rendering-to-a-texture-with-ios-5-texture-cache-api/CFDictionaryRef empty; // empty value for attr value.CFMutableDictionaryRef attrs;empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // our empty IOSurface properties dictionaryattrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);CFDictionarySetValue(attrs, kCVPixelBufferIOSurfacePropertiesKey, empty);CVReturn err = CVPixelBufferCreate(kCFAllocatorDefault, (int)_size.width, (int)_size.height, kCVPixelFormatType_32BGRA, attrs, &renderTarget);if (err){NSLog(@"FBO size: %f, %f", _size.width, _size.height);NSAssert(NO, @"Error at CVPixelBufferCreate %d", err);}err = CVOpenGLESTextureCacheCreateTextureFromImage (kCFAllocatorDefault, coreVideoTextureCache, renderTarget,NULL, // texture attributesGL_TEXTURE_2D,_textureOptions.internalFormat, // opengl format(int)_size.width,(int)_size.height,_textureOptions.format, // native iOS format_textureOptions.type,0,&renderTexture);

其中coreVideoTextureCache是CVOpenGLESTextureCacheRef类型的属性,也就是说,renderTarget的内存,并不是自己创建的,而是来自OpenGLESTextureCache,在调用CFRelease时也不会自行释放。如果不知道其中的原理,自行创建GPUImageFramebuffer,dealloc时并没有真正释放内存,会造成内存泄漏,而且每次都是一帧视频或者一幅图像的大小,相当可观。

而在GPUImageFramebufferCache的purgeAllUnassignedFramebuffers方法中,会帮我们清空OpenGLESTextureCache,真正释放GPUImageFramebuffer占用内存。purgeAllUnassignedFramebuffers方法会在收到memory warning时触发释放内存,一般情况下无需自行调用。

所以,GPUImage给我们实现了一套完善的framebuffer的cache机制,如果不用它而是自行创建和管理framebuffer去处理视频和大量图片时,稍有不慎就会出现crash的情况。在这种情况下出现的crash并不会抛出异常,在xcode提供的内存检测工具中也不能观测到内存增长,会让不明就里的人难以定位crash的原因。

关于CVOpenGLESTextureCache

对于 iOS 5.0+ 的设备,Core Video 允许 OpenGL ES 的 texture 和一个 image buffer 绑定,从而省略掉创建 texture 的步骤,也方便对 image buffer 操作,例如以多种格式读取其中的数据而不是用 glReadPixels 这样比较费时的方法。Core Video 中的 OpenGL ES texture 类型为 CVOpenGLESTextureRef,定义为

A texture-based image buffer that supplies source image data to OpenGL ES.

image buffer 类型为 CVImageBufferRef,在文档中可以看到两个类型其实是一回事:

typedef CVImageBufferRef CVOpenGLESTextureRef;

这些 texture 是由 CVOpenGLESTextureCache 缓存、管理的。可以用 CVOpenGLESTextureCacheCreateTextureFromImage 来从 image buffer 得到 texture 并将两者绑定,该 texture 可能是新建的或缓存的但未使用的。用 CVOpenGLESTextureCacheFlush 来清理未使用的缓存。

以上的 image buffer 需要满足一定条件:

To create a CVOpenGLESTexture object successfully, the pixel buffer passed to CVOpenGLESTextureCacheCreateTextureFromImage() must be backed by an IOSurface.

camera API 得到的 image buffer(CVPixelBufferRef)已经满足条件,在 Apple 的官方 sample code 中有从视频文件的一帧 image buffer 映射到相应 texture 并在 shader 中使用的示例。

但如果要自己创建空的 image buffer 并和 texture 绑定用来 render,那么创建时需要为 dictionary 指定一个特殊的 key:kCVPixelBufferIOSurfacePropertiesKey。代码示例:

CFDictionaryRef empty; // empty value for attr value.
CFMutableDictionaryRef attrs;
empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // our empty IOSurface properties dictionary
attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(attrs, kCVPixelBufferIOSurfacePropertiesKey, empty);CVPixelBufferRef renderTarget;
CVReturn err = CVPixelBufferCreate(kCFAllocatorDefault, (int)_size.width, (int)_size.height, kCVPixelFormatType_32BGRA, attrs, &renderTarget);

转载于:https://www.cnblogs.com/tmacforever/p/9116951.html

GPUImage源码解读之GPUImageFramebufferCache相关推荐

  1. GPUImage源码解读之GPUImageFramebuffer

    简介 OpenGL ES的FrameBuffer是渲染发生的地方,普通的2D图形的渲染默认发生在屏幕上:而三维的图形渲染则除了包括像素点的颜色,还有Depth Buffer,Stencil Buffe ...

  2. Bert系列(二)——源码解读之模型主体

    本篇文章主要是解读模型主体代码modeling.py.在阅读这篇文章之前希望读者们对bert的相关理论有一定的了解,尤其是transformer的结构原理,网上的资料很多,本文内容对原理部分就不做过多 ...

  3. Bert系列(三)——源码解读之Pre-train

    https://www.jianshu.com/p/22e462f01d8c pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现 ...

  4. linux下free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  5. nodeJS之eventproxy源码解读

    1.源码缩影 !(function (name, definition) { var hasDefine = typeof define === 'function', //检查上下文环境是否为AMD ...

  6. PyTorch 源码解读之即时编译篇

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者丨OpenMMLab 来源丨https://zhuanlan.zhihu.com/ ...

  7. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

  8. Feflow 源码解读

    Feflow 源码解读 Feflow(Front-end flow)是腾讯IVWEB团队的前端工程化解决方案,致力于改善多类型项目的开发流程中的规范和非业务相关的问题,可以让开发者将绝大部分精力集中在 ...

  9. spring-session源码解读 sesion

    2019独角兽企业重金招聘Python工程师标准>>> spring-session源码解读 sesion 博客分类: java spring 摘要: session通用策略 Ses ...

  10. 前端日报-20160527-underscore 源码解读

    underscore 源码解读 API文档浏览器 JavaScript 中加号操作符细节 抛弃 jQuery,拥抱原生 JS 从 0 开始学习 GitHub 系列之「加入 GitHub」 js实现克隆 ...

最新文章

  1. Mysql创建数据库用户
  2. VMware HA实战攻略之四VMware HA安装及配置
  3. Java中几种常见的循环
  4. [mybatis]动态sql_if_where_trim判断OGNL
  5. IOS开发学习记录第4天之C语言学习
  6. 计算机考研哪个专业代码少,考研专业代码到底是什么意思
  7. 小白设计模式:责任链模式
  8. Linux系统安全防护加固
  9. AngularJS控制器中的#39;this#39;与$ scope
  10. Python机器学习——如何shuffle一个数据集(ndarray类型)
  11. 深入了解VPP关键技术有哪些?
  12. Python使用matplotlib可视化模拟烧烤摊每月营业额折线图
  13. 跨浏览器共享数据_不懂浏览器安全,被老板邀请爬山?
  14. java十二星座 (快来测试你是什么星座吧)
  15. ArduinoUNO实战-第十三章-步进电机驱动实验
  16. 图片去黑底原理(做个笔记)
  17. has an unsupported return type
  18. 人际关系-《你为什么不道歉》书中的精髓:道歉对于人际关系的重要性,以及怎样传达有诚意、有深度的道歉。
  19. upperbound找不到_upper_bound 和lower_bound 的返回值问题
  20. 20221223英语学习

热门文章

  1. WORD必学技巧:使用项目编号
  2. 为什么很少人写类似金庸的武侠小说
  3. 管理感悟:宁可五个阶段做三个月,绝不一个阶段做二个月
  4. 智力与联系能力的关系
  5. python 写配置文件,python配置文件写入过程详解
  6. 13防更新描述文件_描述文件终于来了,iOS 13 也能屏蔽更新!
  7. c# try-catch 抓不到的Bug 解决方案
  8. php限制单设备登录,app限制一个账号只能单设备登陆
  9. java 当前时间 long_java 获取当前的日期、时间, 日期、字符串、long之间的相互转换...
  10. php中怎么创建对象,php创建对象有哪几种方法