上一篇文章中,介绍了整个GPUImage的整体框架和渲染流程。在整个渲染流程中,GPUImageFrameBuffer
作为一个纽带,将各个不同的元素串联起来;同时,还作为OpenGL ES的渲染媒介,实现了渲染的功能。因此在本文中主要介绍一下GPUImageFrameBuffer
以及GPUImageFrameBufferCache
。
OpenGL ES FrameBuffer
OpenGL ES的FrameBuffer是渲染发生的地方,普通的2D图形的渲染默认发生在屏幕上;而三维的图形渲染则除了包括像素点的颜色,还有Depth Buffer,Stencil Buffer等其他空间。因此,FrameBuffer就是一个这些Buffer的一个集合。
默认情况下,FrameBuffer存在于现存中,但是当需要进行多次渲染或者离屏渲染的时候,可以通过创建一个离屏的FrameBuffer进行渲染。当需要渲染3D效果时,除了创建frameBuffer以外,还需要创建相应的Render Buffer, Depth Buffer, Stencil Buffer,并且将它们attach到frameBuffer上;而当只需要渲染2D图形时,可以直接生成一个Texture,并且将FrameBuffer渲染的结果放入Texture中。
生成一个FrameBuffer的代码:
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
由于存在离屏渲染的情况,实际渲染开始之前,需要先激活目标FrameBuffer,而在渲染完成之后,需要将FrameBuffer绑定为0,回到默认的FrameBuffer,才能够显示在屏幕上:
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
//Rendering Code
glBindFramebuffer(GL_FRAMEBUFFER, 0);
如果要渲染一个2D图像到一个Texture上,则还需要先生成一个Texture,并且将Texture绑定到FrameBuffer上。当使用FrameBuffer进行流的传递的时候,则可以使用这个Texture:
glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &_texture);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// This is necessary for non-power-of-two textures
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, _texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)_size.width, (int)_size.height, 0,GL_RGBA, GL_UNSIGNED_BYTE, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
GPUImageFrameBuffer
GPUImageFrameBuffer不只是简单的对OpenGL ES的FrameBuffer的对象化封装,而是一个对渲染对象的对象化封装。它生成的可以是一个带有Texture作为Attachment的FrameBuffer,也可以仅仅生成一个Texture作为渲染对象。它的主要功能有:
- 生成渲染对象,FrameBuffer或者Texture,带的参数包括size和texture option,这两个参数主要是用于FrameBuffer的重用,之后会介绍到:
- (id)initWithSize:(CGSize)framebufferSize;
- (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture;
- (id)initWithSize:(CGSize)framebufferSize overriddenTexture:(GLuint)inputTexture;
- FrameBuffer的管理,在渲染的时候激活frameBuffer:
- (void)activateFramebuffer;
- FrameBuffer的引用计数。同样用于FrameBuffer的重用,接下来会详细介绍:
- (void)lock;
- (void)unlock;
- (void)clearAllLocks;
- (void)disableReferenceCounting;
- (void)enableReferenceCounting;
- 从FrameBuffer中生成图片或者源数据:
- (CGImageRef)newCGImageFromFramebufferContents;
- (GLubyte *)byteBuffer;
GPUImageFrameBuffer的引用计数
由于GPUImageFrameBuffer需要重用,因此,当FrameBuffer的引用为0的时候,这个FrameBuffer就会被放到Cache中,来给其他的需要的地方进行使用。因此作者给GPUImageFrameBuffer引入了引用计数的概念。
引用计数的概念其实和OC自带的MRC有点类似,每当一个地方需要使用这个FrameBuffer的时候,手动调用lock方法,使引用计数+1;当渲染完毕或者这个FrameBuffer使用完毕的时候,手动调用unlock方法,使引用计数-1;当引用计数为0的时候,FrameBuffer会被归还到Cache中。
调用增加FrameBuffer引用计数方法的地方:
- 每次从Cache中获取到一个FrameBuffer的时候,都会调用一次lock方法,因为既然获取了FrameBuffer就是需要使用它进行渲染的时候,因此这个是最合适的调用时机;
- 将一个FrameBuffer作为Output传给下一个Input的时候,会调用
setInputFrameBuffer:atIndex:
方法。因为这个FrameBuffer会被传递给下一个input进行使用,因此我们需要手动的将这个FrameBuffer的引用计数+1; - 如果需要将一个Filter的输出进行截图的话(
usingNextFrameForImageCapture == YES
),也需要手动调用lock方法。因为默认情况下,当下一个input渲染完成之后,就会释放这个FrameBuffer。如果你需要对当前的Filter的输出进行截图的话,则需要保留住这个FrameBuffer。
调用减少FrameBuffer引用计数方法的地方:
- 在
renderToTextureWithVertices:textureCoordinates:
渲染完成之后,需要将从上一个Output中传过来的所有InputFrameBuffer都unlock掉; - 在informTargetsAboutNewFrameAtTime:通知下一个input之后,需要将当前的这个Output中的outputFrameBuffer给unlock掉;
不需要引用计数的情况:
在有些地方我们是不需要使用引用计数的,比如GPUImagePicture
。因为生成了Picture之后,它只会有一个outputFrameBuffer。而且这个frameBuffer只有在GPUImagePicture
初始化的时候生成。因此这个FrameBuffer不能被释放,否则以后它的target就不能使用了。因此GPUImagePicture
初始化的时候调用了disableReferenceCounting
方法。只有当这个GPUImagePicture在dealloc的时候才能被释放。
GPUImageFrameBufferCache
由于GPUImage添加滤镜可以形成一个FilterChain,因此,在渲染的过程中,可能会需要很多个FrameBuffer,但是正如上文所说,每生成一个FrameBuffer都需要占用一定的内存或者显存。因此,必须保证尽可能少创建FrameBuffer。而GPUImageFrameBufferCache
就是用来管理所有的FrameBuffer的。
根据上面对GPUImageFrameBuffer的介绍,每个FrameBuffer其实就是一块内存或者缓存,因此只要它们的size
和textureOption
是一样的,那么这个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中以便重用。
GPUImageFrameBufferCache可以创建多个,一般每一个GPUImageContext中会有一个公用的GPUImageFrameBufferCache。通过这个Cache可以获得对应的GPUImageContext中得到对应的FrameBuffer对象。