GPUImage的filter的textures处理链式结构
两个最重要的的地方:
最重要的一个类GPUImageOutput(所有的filter的父类,其他也有继承它的,如GPUImageUIElement,UIKit元素通过CG转gles贴图 等等);
协议(或者接口)GPUImageInput。
继承GPUImageOutput且遵循GPUImageInput的filter,处理完成后输出又可以作为下一个filter的输入。
@protocol GPUImageInput <NSObject> - (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex; - (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex; - (NSInteger)nextAvailableTextureIndex; - (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex; - (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex; - (CGSize)maximumOutputSize; - (void)endProcessing; - (BOOL)shouldIgnoreUpdatesToThisTarget; - (BOOL)enabled; - (BOOL)wantsMonochromeInput; - (void)setCurrentlyReceivingMonochromeInput:(BOOL)newValue; @end
GPUImageFramebuffer
framebuffer的封装类,根据onlyGenerateTexture 判断 只生成纹理 或 framebuffer;摘自 - (void)generateFramebuffer;
只生成纹理的情况典型:GPUImageUIElement,GPUImageVideoCamera等等;
生成framebuffer,判断是否支持快速上传纹理数据(其实是判断CVOpenGLESTextureCacheCreate是否可用)
- 如果支持快速上传纹理,CVPixelBufferCreate生成renderTarget,CVOpenGLESTextureCacheCreateTextureFromImage根据renderTarget(sourceImage)生成renderTexture,最后调用glFramebufferTexture2D将framebuffer和renderTexture绑定在一块,framebuffer输出到texture(注:framebuffer也可以绑定到renderBuffer,也常称为colorbuffer,renderbuffer直接显示在CALayer上了;绑定在texture上通常作为中间值);
- 如果不支持;先generate texture,再绑定,glTexImage2D上传数据到GPU,最后调用glFramebufferTexture2D将framebuffer和texture绑定在一块;
GPUImageFramebuffer中的- (CGImageRef)newCGImageFromFramebufferContents;,用于从framebuffer中取出图像数据生成CGImageRef;
CGDataProviderRef dataProvider = NULL; if ([GPUImageContext supportsFastTextureUpload]) { #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE NSUInteger paddedWidthOfImage = CVPixelBufferGetBytesPerRow(renderTarget) / 4.0; //字节对齐后的图片占用宽度可能要大 NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)_size.height * 4; glFinish(); //强制提交前面调用的gl指令到GPU硬件,阻塞调用 CFRetain(renderTarget); //防止出现野指针,在回调中释放 I need to retain the pixel buffer here and release in the data source callback to prevent its bytes from being prematurely deallocated during a photo write operation [self lockForReading]; rawImagePixels = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget); dataProvider = CGDataProviderCreateWithData((__bridge_retained void*)self, rawImagePixels, paddedBytesForImage, dataProviderUnlockCallback); //全局的framebuffercache强引用当前自身,防止framebuffer在切换时出现问题 [[GPUImageContext sharedFramebufferCache] addFramebufferToActiveImageCaptureList:self]; // In case the framebuffer is swapped out on the filter, need to have a strong reference to it somewhere for it to hang on while the image is in existence #else #endif } else { [self activateFramebuffer]; rawImagePixels = (GLubyte *)malloc(totalBytesForImage); glReadPixels(0, 0, (int)_size.width, (int)_size.height, GL_RGBA, GL_UNSIGNED_BYTE, rawImagePixels); //阻塞调用,直接从framebuffer中读取image 原始数据 dataProvider = CGDataProviderCreateWithData(NULL, rawImagePixels, totalBytesForImage, dataProviderReleaseCallback); [self unlock]; // Don't need to keep this around anymore }
最后CGImageCreate生成图片返回。
GPUImageOutput
类的说明其实已经很明了,视频采集,拍照等都是以它为基类,同一套路:源(视频,静态图)上传图片帧给OpenGL ES作为textures,这些textures作为下一个filter的输入,形成处理texture的链式结构。
/** GPUImage's base source object Images or frames of video are uploaded from source objects, which are subclasses of GPUImageOutput. These include: - GPUImageVideoCamera (for live video from an iOS camera) - GPUImageStillCamera (for taking photos with the camera) - GPUImagePicture (for still images) - GPUImageMovie (for movies) Source objects upload still image frames to OpenGL ES as textures, then hand those textures off to the next objects in the processing chain. */
类中工具函数runSynchronouslyOnContextQueue等,通过dispatch_get_specific防止死锁,注意不要用dispatch_get_current_queue;
通过对三个典型类的作用解读,分别为 (GPUImagePicture)source->(GPUImageFilter)filter->(GPUImageView)output,形成处理链式结构,当然还有其他的pipeline。
1,GPUImagePicture
GPUImagePicture只继承GPUImageOutput,专门用作读取输入数据,上传GPU,交个链条下一步GPUImageFilter处理。
初始化initWithCGImage:读取CGImageRef数据,判断是否需要CG的辅助来处理图片数据,如需要CG,固定套路(包含解压图片语义)CGBitmapContextCreate–>CGContextDrawImage;不需要的情况,CGImageGetDataProvider–>CGDataProviderCopyData–>CFDataGetBytePtr获取原始数据, 最后通过调用glTexImage2D上传imageData到当前outputFramebuffer 的GPU texture;
处理图片processImageWithCompletionHandler:根据addTarget加入的所有target(即链条结构中间的filter),逐个setInputFramebuffer设置当前outputFramebuffer中processed texture为下一步filter的输入;
newFrameReadyAtTime 通知各个加入的target处理数据。
2,GPUImageFilter
GPUImageFilter(实际开发中通常用到它的子类)继承GPUImageOutput,同时遵循GPUImageInput 协议,类的说明如下
/** GPUImage's base filter class Filters and other subsequent elements in the chain conform to the GPUImageInput protocol, which lets them take in the supplied or processed texture from the previous link in the chain and do something with it. Objects one step further down the chain are considered targets, and processing can be branched by adding multiple targets to a single output or filter. */
GPUImageFilter中 setInputFramebuffer (GPUImageInput协议方法)简单地赋值 ;
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex; { firstInputFramebuffer = newInputFramebuffer; [firstInputFramebuffer lock]; }
然后调用newFrameReadyAtTime;
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex; { static const GLfloat imageVertices[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, }; //顶点数据,两个三角形组成texture区域 [self renderToTextureWithVertices:imageVertices textureCoordinates:[[self class] textureCoordinatesForRotation:inputRotation]]; [self informTargetsAboutNewFrameAtTime:frameTime]; }
激活该filter中的 filterProgram(已经attach过 顶点shader 和 片元shader),然后绑定输入的texture并渲染。
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates; { if (self.preventRendering) { [firstInputFramebuffer unlock]; return; } [GPUImageContext setActiveShaderProgram:filterProgram]; /** * 从GPUImageFrameBufferCache中取出可重用的outputFramebuffer * **/ outputFramebuffer = [[GPUImageContext sharedFramebufferCache] fetchFramebufferForSize:[self sizeOfFBO] textureOptions:self.outputTextureOptions onlyTexture:NO]; [outputFramebuffer activateFramebuffer]; if (usingNextFrameForImageCapture) { [outputFramebuffer lock]; } [self setUniformsForProgramAtIndex:0]; glClearColor(backgroundColorRed, backgroundColorGreen, backgroundColorBlue, backgroundColorAlpha); glClear(GL_COLOR_BUFFER_BIT); glActiveTexture(GL_TEXTURE2); //选择GL_TEXTURE2 glBindTexture(GL_TEXTURE_2D, [firstInputFramebuffer texture]); //绑定当前输入的framebuffer中的texture glUniform1i(filterInputTextureUniform, 2); //分别设置顶点shader中的顶点数据,和将来用于片元shader中的texture坐标数据 glVertexAttribPointer(filterPositionAttribute, 2, GL_FLOAT, 0, 0, vertices); glVertexAttribPointer(filterTextureCoordinateAttribute, 2, GL_FLOAT, 0, 0, textureCoordinates); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); [firstInputFramebuffer unlock]; if (usingNextFrameForImageCapture) { dispatch_semaphore_signal(imageCaptureSemaphore); } }
informTargetsAboutNewFrameAtTime 中轮询两次当前的target,第一次大致还是调用父类的setInputFramebufferForTarget(父类GPUImageOutput),第二次继续newFrameReadyAtTime,又回到了从source添加target的原点。
3,GPUImageView
作为最终的输出target只实现了GPUImageInput的协议,只能接受source或者filter传过来的数据,不再作为输出了;
其中的setInputFramebuffer 和 newFrameReadyAtTime和filter中处理如出一辙,但是加了一个调用;如下,正如开头提到的framebuffer也可以绑定到renderBuffer,也常称为colorbuffer,renderbuffer直接显示在CAEAGLLayer上了;最终通过设置屏幕大小的缓冲区,直接显示在手机屏幕上。
- (void)presentFramebuffer; { glBindRenderbuffer(GL_RENDERBUFFER, displayRenderbuffer); [[GPUImageContext sharedImageProcessingContext] presentBufferForDisplay]; }
其中的displayRenderBuffer通过createDisplayFramebuffer方法创建,都是些模板代码,没什么可记录的。
小结
GPUImage的代码结构可谓是链式处理结构的典范,很值得学习;本文只记录了processing chain(source->filter–>filter…->output)的数据流向,很多细节以后再记录。
参考
GPUImage源码:https://github.com/BradLarson/GPUImage