iOS图像处理系列 - GPUImage源码解读(二)

导语 :billzbwang写的《iOS 图像处理系列 - GPUImage源码解读(一)》里详细地介绍了核心代码的具体实现,对GPUImage的使用者有很大的参考价值。在GPUImage中,有一个不为大多数人所注意却又极其重要的模块GPUImageFramebufferCache。在《iOS 图像处理系列 - GPUImage源码解读》系列的第二篇中,我就把自己对这一模块粗浅的了解分享给大家。

GPUImageFramebufferCache这个类主要的作用是对GPUImageFramebuffer的管理。这个类对外暴露了如下5个接口。

其中我们主要使用fetchFramebufferForSize的两个方法。

我们一般会在制作自定义滤镜时用到这个类,在GPUImageFilter的使用这个类时,会在- (void)renderToTextureWithVertices:textureCoordinates:中以如下的形式获取一个GPUImageFramebuffer。

outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO];

在获取一个分framebuffer后,我们会调用framebuffer的lock方法加锁以防Framebuffer回收,使用完毕后再调用unlock解锁。在GPUImageFramebuffer类的内部,则使用了一个名为framebufferReferenceCount的变量做引用计数,为0时调用GPUImageFramebufferCache的returnFramebufferToCache方法告知GPUImageFramebufferCache回收framebuffer。

由此,我们可以看到,GPUImage为我们提供了一套完善的framebuffer的cache机制,从GPUImageContext的sharedFramebufferCache里取一个framebuffer,当没有人使用时再自动还回Cache。

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

答案显示是NO。

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

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

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

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

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

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

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

下面让我们看一下GPUImageFramebufferCache的实现。GPUImageFramebufferCache通过以下方法去获取一个cache里的GPUImageFramebuffer。

而其中的hashForSize:textureOptions:onlyTexture:方法如下。

可以看出,当framebuffer的size和textureOptions、onlyTexture相同时,GPUImageFramebufferCache优先从cache中拿一个已有的framebuffer出来,在没有的情况下则创建一个新的。

我们在处理视频时,处理每帧时都会申请framebuffer,从而造成连续申请GPUImageFramebuffer,而这样申请到的framebuffer一般是从GPUImageFramebufferCache中拿到的已经使用过的,如果在使用时为了追求效率不对每个点赋值可能出现所谓的“灵异问题”。

总结一下,我们在使用GPUImageFramebuffer时,要注意两点,一是通过GPUImageFramebufferCache获取framebuffer,以免出现内存没有释放的问题;二是从Cache中获取framebuffer后,要注意framebuffer里的脏数据造成的问题。


作者简介:ygdai(戴阳刚),天天P图iOS工程师

原文发布于微信公众号 - 天天P图攻城狮(ttpic_dev)

原文发表时间:2017-06-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏芋道源码1024

重磅:JDK 11 正式发布!东半球第二全特性解读!

千呼万唤,JDK11于2018-09-25正式发布!你是不是和笔者一样还在使用JDK8呢?甚至有些开发者还在使用JDK7!没关系,让我们先一睹JDK11的风采。

1462
来自专栏LinXunFeng的专栏

iOS 面向协议方式封装空白页功能

1935
来自专栏AhDung

【VBS】vbs指定编码保存文本文件(含xml、ini什么的)

- 让用户填写一些信息,待安装完成后把这些信息写入软件安装目录中的指定ini、xml文件中

1131
来自专栏小狼的世界

locale的详细解释

* Thu Sep 27 2001 Bernhard Rosenkraenzer 2.5-0.f.2

904
来自专栏更流畅、简洁的软件开发方式

【自然框架】注册会员活动——第一份代码的修改建议(第一版)

  感谢“好坏”提供代码,这是我看过的比较不错的三层结构的代码了,业务层并不是直接调用DAL,而是有其自身的逻辑判断,并不是传声筒,很赞。 我对这份代码,按照自...

2246
来自专栏Java 技术分享

Web 小案例 -- 网上书城(三)

44610
来自专栏Windows Community

Windows Community Toolkit 3.0 - Gaze Interaction

Gaze Input & Tracking - 也就是视觉输入和跟踪,是一种和鼠标/触摸屏输入非常不一样的交互方式,利用人类眼球的识别和眼球方向角度的跟踪,来判...

1323
来自专栏Flutter入门

RecyclerView 源码分析-开编

看了又看,任然对其一知半解。用了又用,发现其真的太美。RecyclerView的设计和书写实在是太惊艳了,日常又使用的相当频繁。虽然之前就看过其他的源码分析,故...

2822
来自专栏java一日一条

编写可靠 Shell 脚本的 8 个建议

这八个建议,来源于键者几年来编写 shell 脚本的一些经验和教训。事实上开始写的时候还不止这几条,后来思索再三,去掉几条无关痛痒的,最后剩下八条。毫不夸张地说...

1152
来自专栏更流畅、简洁的软件开发方式

Attribute(特性),怎么用才更好?

前几年:   2008年的某一天,我坐火车去北京。硬卧上铺,一晚上就到北京了。爬到上铺之后发现,旁边上铺有一老兄抱着一个笔记本,一开始还以为是看电影呢,仔细一...

2549

扫码关注云+社区

领取腾讯云代金券