10分钟带你全面了解GPUImage框架模块功能

734 阅读9分钟

众所周知,GPUImage是一个著名的图像处理开源库,里面提供了多达上百种的滤镜效果来加工图像,并且支持图片和视频的实时滤镜,满足我们日常美颜/滤镜需求。对这样强大的开源框架进行分析,一方面可以帮助我们在自己的项目上定制我们滤镜效果,另一方面源码中涉及的AVFoundation框架应用、图像处理逻辑、OpenGL API调用都是我们学习渲染非常实用的案例,值得仔细学习。

本篇文章主要对GPUImage框架进行一个整体的分析,了解框架的模块划分和功能概况,更多细节的讨论将在后续文章中逐步分析。

GPUImage框架的源码对模块的划分还是比较清晰的,主要分为了以下四个部分:

  • 上下文环境,包括运行GPUImage的上下文定义、资源定义、缓存管理相关类都包括在其中
  • 输入源,即滤镜处理链路的源头,包括视频、图片在内的各种输入源都定义其中
  • 输出源,即处理链路的尽头,用于将处理后的数据绘制到屏幕、或者转成二进制数据推流等等
  • 滤镜,提供多达上百种的滤镜效果使用来进行图像处理

接下来我们逐一进行分析:

1. 上下文环境

1.1 GPUImageContext

GPUImageContext是一个单例对象,用于管理渲染过程中的资源,包括:

  • EAGLContext上下文
@property(readonly, retain, nonatomic) EAGLContext *context;
  • 着色器程序
// 当前正在使用的着色器程序
@property(readwrite, retain, nonatomic) GLProgram *currentShaderProgram;

// 从着色器缓存中查询对应着色器,如果未nil则创建
- (GLProgram *)programForVertexShaderString:(NSString *)vertexShaderString fragmentShaderString:(NSString *)fragmentShaderString;
  • FrameBuffer缓存
// 渲染过程中在创建FrameBuffer时会优先从缓存中获取符合尺寸要求的frameBuffer,避免重复创建
@property(readonly) GPUImageFramebufferCache *framebufferCache;
  • 线程队列
// 在初始化上下文时,GPUImage会自动创建一个串行队列,渲染任务都将抛到该串行队列中执行
@property(readonly, nonatomic) dispatch_queue_t contextQueue;

1.2 GLProgram

GPUImage对图片的处理和渲染主要是由OpenGL ES实现的,GLProgram则是对OpenGL ES program的OOP封装,以方便调用。核心功能如下:

  • 着色器程序的编译、链接、使用及日志反馈
// 着色器编译
- (id)initWithVertexShaderString:(NSString *)vShaderString 
            fragmentShaderString:(NSString *)fShaderString;
// 着色器链接
- (BOOL)link;
// 着色器使用
- (void)use;
// 着色器有效性校验
- (void)validate;
  • 着色器程序的有效性校验
// 着色器有效性校验
- (void)validate;
  • attribute、uniform变量字符串映射
// 变量名映射到变量索引
- (GLuint)attributeIndex:(NSString *)attributeName;
- (GLuint)uniformIndex:(NSString *)uniformName;

1.3 GPUImageFrameBuffer

GPUImageFrameBuffer本质是对OpenGL frameBuffer和Texture的OOP封装。

帧缓冲区(frameBuffer)可以看作是一块画板,但不是画布。它只作为容器,因此frameBuffer本身并不存储数据,数据依然是存储在画布上,而画布可以是纹理(Texture)或者是渲染缓冲区(RenderBuffer),在frameBuffer上这些位置称为附件(Attachment)。

因此,GPUImageFrameBuffer实际上管理着frameBuffer和纹理数据。其核心接口如下:

  • FrameBuffer初始化
// @param framebufferSize指定纹理尺寸
// @param fboTextureOptions指定纹理参数
// @param onlyGenerateTexture指定是否只创建texture而不创建FrameBuffer
- (id)initWithSize:(CGSize)framebufferSize 
	textureOptions:(GPUTextureOptions)fboTextureOptions 
	   onlyTexture:(BOOL)onlyGenerateTexture;
  • FrameBuffer激活
// 在使用frameBuffer前需要先激活
- (void)activateFramebuffer
  • 引用计数
// 引用计数相关接口,当frameBuffer的引用计数为0时,会自动放入缓存池
- (void)lock;
- (void)unlock;
- (void)clearAllLocks;
- (void)disableReferenceCounting;
- (void)enableReferenceCounting;
  • 数据读取
// 获取frameBuffer附件中的pixelBuffer数据
- (NSUInteger)bytesPerRow;
- (GLubyte *)byteBuffer;
- (CVPixelBufferRef)pixelBuffer;

1.4 GPUImageFrameBufferCache

GPUImageFrameBuffer缓存池,实际上负责渲染过程中GPUImageFrameBuffer的创建和回收

// 获取指定纹理尺寸和参数的frameBuffer,如果不存在则会创建一个新的,并放入缓存池
- (GPUImageFramebuffer *)fetchFramebufferForSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)textureOptions onlyTexture:(BOOL)onlyTexture;

// frameBuffer使用完成后放入缓存池中
- (void)returnFramebufferToCache:(GPUImageFramebuffer *)framebuffer;

2. 输入源Source

GPUImage本质上是一个OpenGL管理框架,它不仅提供了大量OpenGL滤镜实现,同时也提供了一套滤镜的链式调用流程,类似于工厂的流水线。而输入源即是流水线的起点,即原料。输入源继承自GPUImageOutput来实现GPUImageFramebuffer的输出行为

2.1 GPUImageOutput

2.1.1 关键属性

// 输入转换成OutputFrameBuffer
GPUImageFramebuffer *outputFramebuffer;

// 记录frameBuffer输出的目标
NSMutableArray *targets;

// 记录frameBuffer输出到目标的那个索引位置的frameBuffer上(某些滤镜可以接受多个输入源,因此需要记录是输出到哪个位置)
NSMutableArray *targetTextureIndices

2.1.2 关键方法

// 添加一个输出目标
- (void)addTarget:(id<GPUImageInput>)newTarget;

// 移除一个输出目标
- (void)removeTarget:(id<GPUImageInput>)targetToRemove;

// 将OutputFramebuffer赋值到target的指定索引的frameBuffer上,也就是把上一个滤镜处理好的数据传递到下一个滤镜中作为输入)
- (void)setInputFramebufferForTarget:(id<GPUImageInput>)target atIndex:(NSInteger)inputTextureIndex;

2.2 输入源类型

输入源类型说明
GPUImageRawDataInput二进制图片数据将二进制数据通过glTexImage2D加载到输出纹理中
GPUImageUIElementUIView或CALayer数据将显示的内容绘制到CoreGraphics上下文,再通过glTexImage2D加载
GPUImageVideoCamera视频数据拿到视频录制数据CMSamleBufferRef通过YUV/RGB纹理生成流程加载到frameBuffer
GPUImageMovie本地视频拿到视频每帧数据再通过YUV/RGB纹理生成流程加载到frame
GPUImageTextureInput已存在的纹理将已存在的纹理直接绑定到frameBuffer上
GPUImagePicture本地图片获取图片的二进制数据再通过glTexImage2D加载到纹理上

3. 输出源Output

前面提到输入源是流水线的起点,那么输出源就是链路的终点了。

3.1 GPUImageInput协议

GPUImageInput是一个协议,实现该协议的对象具备接受frameBuffer输入,从而展示纹理(例如上屏)或者传递纹理到下一个target的能力,输出源和中间滤镜均实现了该协议。

3.1.1 核心方法

  • 设置输入frameBuffer数据
// 设置该滤镜对应索引位置的输入frameBuffer
- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex;
  • 启动下一帧渲染
// 通知该节点的target输入frameBuffer已经准备就绪,可以开始下一帧渲染
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;

3.2 输出源类型

输出源类型说明
GPUImageView继承自UIView处理后的图像直接渲染到指定的原生view上
GPUImageMovieWriter封装AVAssetWriter将处理后的视频数据逐帧写入指定路径文件中
GPUImageRawDataOutput二进制数据获取处理后纹理的二进制数据,可用于上行推流等等
GPUImageTextureOutput纹理数据每一帧渲染结束后,通过texture属性返回输入纹理的索引

输出源实现了GPUImageInput协议,在newFrameReadyAtTime:atIndex:方法中启动下一帧渲染时,执行上屏、写入文件或者返回二进制数据等操作,而不进行纹理数据的传递,因此它是滤镜链的终点。

4. 滤镜Filters

滤镜即是GPUImage框架的核心,每个滤镜Filter实现了一种图像处理的能力,例如提高亮度、对比度、饱和度等等,GPUImage框架包括了上百种滤镜,可以根据需求选择。

4.1 GPUImageFilter

GPUImage中的滤镜均继承自GPUImageFilter,其定义了一个滤镜处理的基本流程GPUImageFilter继承自GPUImageOutput,同时实现了GPUImageInput协议,这就使得GPUImageFilter即可以接收frameBuffer输入进行图形处理,同时可以将处理后的数据传递到下一个target进行后续处理,实现了数据流的传递。

@interface GPUImageFilter : GPUImageOutput <GPUImageInput>

4.1.1 核心方法

  • 初始化滤镜
// 初始化滤镜,包括生成GLPrograme渲染程序,初始化shader程序中变量索引等
- (id)initWithVertexShaderFromString:(NSString *)vertexShaderString fragmentShaderFromString:(NSString *)fragmentShaderString;
  • 启动渲染
// GPUImageInput协议实现,启动一帧渲染
- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex;
  • 渲染逻辑
// 该滤镜根据GLProgram进行图像处理的真正渲染逻辑
- (void)renderToTextureWithVertices:(const GLfloat *)vertices textureCoordinates:(const GLfloat *)textureCoordinates;
  • 通知targets启动下一帧渲染
// 通知该filter连接的所有targets进行下一帧数据处理
- (void)informTargetsAboutNewFrameAtTime:(CMTime)frameTime

4.2 滤镜分类

前面提到GPUImage框架包括了上百种Filter,官方实际上也对这些滤镜进行了分类(每个分类仅列举部分代表性滤镜),具体的实现逻辑读者可以根据需求进行阅读,流程上都是一致的,只是输入输出个数以及shader代码有所不同。

  • 颜色调整类
滤镜作用
GPUImageBrightnessFilter调整图像的亮度
GPUImageExposureFilter调整图像的曝光
GPUImageContrastFilter调整图像的对比度
GPUImageSaturationFilter调整图像的饱和度
GPUImageGammaFilter调整图像的灰度系数
GPUImageRGBFilter调整图像的各个RGB通道
GPUImageHueFilter调整图像的色调
GPUImageWhiteBalanceFilter调整图像的白平衡
GPUImageHistogramFilter分析输入图像并创建输出直方图
  • 图像处理类
滤镜作用
GPUImageTransformFilter对图像应用任意2-D或3-D变换
GPUImageCropFilter将图像裁剪到特定区域
GPUImageSharpenFilter锐化图像
GPUImageGaussianBlurFilter高斯模糊
GPUImageSobelEdgeDetectionFilterSobel边缘检测
GPUImageMotionBlurFilter运动模糊
  • 混合模式类
滤镜作用
GPUImageAddBlendFilter两个图像的叠加混合
GPUImageSubtractBlendFilter两个图像的减法混合
GPUImageDivideBlendFilter两个图像的分割混合
GPUImageMaskFilter使用另一个图像掩盖一个图像
  • 视觉效果类
滤镜作用
GPUImageSketchFilter将视频转换为草图
GPUImageEmbossFilter对图像应用浮雕效果
GPUImagePosterizeFilter卡通着色
GPUImageBulgeDistortionFilter图像捏合扭曲

5. 总结

通过对GPUImage框架模块的逐一分析,我们可以知道GPUImage是采用了责任链模式来实现链式处理。

GPUImage定义了一个GPUImageOutput类和一个GPUImageInput协议,实现了GPUImageInput协议的对象具备接受frameBuffer纹理输入并进行处理的能力,而继承自GPUImageOutput的对象则可以将处理后的输出纹理传递到下一个target的功能。

输入源Input继承自GPUImageOutput,可以将图片、视频等等数据上传到frameBuffer后传递到GPUImageFilter中处理,最后一个Filter在处理完成后,将数据传递到实现了GPUImageInput协议的输出源Output中进行上屏绘制或者上行推流,形成完整的滤镜链路处理。