GPUImage源码解读之GPUImageFramebufferCache
简介
由于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。
因此,查询的过程是:
- 使用HashKey查询到满足条件的FrameBuffer个数:
NSString *lookupHash = [self hashForSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
NSNumber *numberOfMatchingTexturesInCache = [framebufferTypeCounts objectForKey:lookupHash];
NSInteger numberOfMatchingTextures = [numberOfMatchingTexturesInCache integerValue];
- 如果个数为零,则生成一个新的FrameBuffer并且返回:
framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture];
- 如果有满足条件的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];
}
- 在返回FrameBuffer之前,需要将FrameBuffer进行一次lock,增加引用计数。
- 当一个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_IPHONE
if (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 dictionary
attrs = 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 attributes
GL_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);
GPUImage源码解读之GPUImageFramebufferCache的更多相关文章
- GPUImage源码解读之GPUImageContext
GPUImageContext类,提供OpenGL ES基本上下文,GPUImage相关处理线程,GLProgram缓存.帧缓存.由于是上下文对象,因此该模块提供的更多是存取.设置相关的方法. 属性列 ...
- GPUImage源码解读之GLProgram
简述 GLProgram是GPUImage中代表openGL ES 中的program,具有glprogram功能.其实是作者对OpenGL ES program的面向对象封装 初始化 - (id)i ...
- GPUImage源码解读之GPUImageFramebuffer
简介 OpenGL ES的FrameBuffer是渲染发生的地方,普通的2D图形的渲染默认发生在屏幕上:而三维的图形渲染则除了包括像素点的颜色,还有Depth Buffer,Stencil Buffe ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读 之 NSData+ImageContentType
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
- SDWebImage源码解读 之 UIImage+GIF
第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...
- SDWebImage源码解读 之 SDWebImageCompat
第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...
- SDWebImage源码解读_之SDWebImageDecoder
第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
随机推荐
- js原生带缩略图的图片切换效果
js原生带缩略图的图片切换效果 本例中用到的 moveElement(elementID,final_x,final_y,interval)是来自<JavaScript DOM编程艺术(中文第二 ...
- bzoj2119 [ZJOI2010]base基站选址
传送门 n年前的考试题,今天才填上…… 听说你们会决策单调性+主席树?然而我多年不写决策单调性,懒得写了……于是就写了一发线段树. 其实线段树应该不难想,毕竟转移是分层转移,并且这个题的转移函数可以快 ...
- Git更新或提交出错的解决办法
一.舍弃本地代码,用远端版本覆盖本地版本. 当自己本地修改很少,更新代码出现冲突时,“error: Your local changes to the following files would be ...
- node定时任务
var schedule = require('node-schedule') require('shelljs/global'); function scheduleCronstyle(){ sch ...
- 提问的智慧 How To Ask Questions The Smart Way
提问的智慧 How To Ask Questions The Smart Way Copyright © 2001,2006,2014 Eric S. Raymond, Rick Moen 本指南英文 ...
- Linux 命令备忘(持续更新中……)
Linux命令 grep 1. 使用grep 筛选内容,多条件筛选用 grep - E "条件1|条件2" (满足条件1或条件2的均展示) 2. grep '条件3'|grep - ...
- LeetCode-Container With Most Water-zz
先上代码. #include <iostream> #include <vector> #include <algorithm> using namespace s ...
- leetcode-Maximum Product Subarray-ZZ
http://blog.csdn.net/v_july_v/article/details/8701148 假设数组为a[],直接利用动归来求解,考虑到可能存在负数的情况,我们用Max来表示以a结尾的 ...
- [问题记录]libpomelo的安装
1. 描述: 按照github上的操作完成 Windows in your libpomelo project root directory open git bash and type in mkd ...
- 08提权 系统文件权限和远程连接IP绕过 安装后门
大家都知道08权限的系统权限设置很严格 面对限制IP连接的情况 我们及时拿到system权限 有账号也上不去这种情况下只能弄shift后门 或者放大镜了 但08权限 在system权限也操作不了系 ...