在移动端图片处理时,往往因为大量的计算导致需要较长的时间,为了充分利用设备的潜能,所以产生了利用GPU来处理计算的方案。其中最有名的开源方案就是GPUImage。
结构
GPUImage的设计思路参考了流式的结构,所有东西都被概括为input或者output,其中最重要的filter则既是input也是output。这样设计统一了整个流程,但让整个结构变的更加死板和复杂。大概的流程如下:
Image >> Input >> Filter_1 >> Filter_2 >> Filter_x >> Output
当存在多个Filter的时候,整个结构上就会变的比较难以理解,所以个人认为,需要分开的结构还是分开比较好:
Image >> Dispatch >> Output
/ \
/ \
Filter_1 ... Filter_x
性能优化
GPUImage自身在设计的时候就考虑到了优化,所以有些时候你可能会感觉某些结构上有些奇怪。
比如GPU内存申请基本上是在输入端就创建好了,在Filter中是不会新申请内存的,都是重用了开始就申请好的内存,除非改变了画布大小。
Filter最好进行重用,因为OpenGL es首先需要利用CPU将GLSL编译为GPU可执行的代码,最好的情况是能够重用这部分已经编译完成的代码。
当我们利用GPUImage来处理图片时,一般不会有什么性能问题,但是如果我们利用他来处理视频时,就需要考虑到极限下的性能问题。我们来模拟下多个Filter情况下的GPU处理1帧的情景:
Image >> Input >> Filter_1 >> Filter_x >> Output
/ \
Prepare(CPU) >> Process(GPU)
可以看到每个Filter之间是同步执行的,所以无法很好的利用CPU多线程的能力。理想状况下应该是这样:
Image >> Input >> Filter_1 ... Filter_x
| |
Prepare(CPU) >> Prepare(CPU)
| |
Progress(GPU) >> Processor(GPU) >> Output
这样就可以将串行执行改为CPU和GPU并发执行,不过GPUImage目前无法改造为这样的结构。
OpenGL es
目前iOS平台支持的GPU平台编码主要有2种方式,一种是OpenGL es,另一种就是Metal。按照官方说法,Metal的性能会比OpenGL更好,好像是因为在编译期就进行了一次初期编码,将代码转化为一种类似于bitcode一样的中间码。
OpenGL的基本流程如下:
CPU: 创建FrameBuffer + GLSL
| draw
GPU: main()
| readPixel
CPU read to RAM
GLSL
想要了解GPUImage就必须先学会使用OpenGL,那么就离不开学习GLSL,如果你使用CIImage,你也可能会需要KISL,是GLSL的一种子集。
GLSL并不复杂,是一种非常类似于C语言的DSL,但是要与程序进行交互则会变得麻烦。
总结
要实现自己的Filter,则首先需要学习OpenGL以及GLSL,这是一个比较大的障碍点。
如果仅仅是为了图片处理,那么GPUImage已经足够了。如果你需要高分辨率高帧率的视频渲染,GPUImage可能就满足不了你,优化思路是并发和利用空间去换取时间。