存在的问题:
- 1.使用GPUImage第三方框架,对图片进行渲染, 底层是基于OpenGL实现的
- 2.链式渲染的方式,多张图片或者多个结果需要依赖的时候,需要自己编写大量着色器代码, GPUImage只适用于单张图片或者渲染操作数很少的需求
- 3.后续复杂的功能比如特效,小视频等 GPUImage无法满足我们的业务需求
- 4.使用过程中手机发热,耗电量也增加(GPU和CPU负载过重)
探索重构方案:
使用OpenGL和metal来做,但是两个哪个最好呢?
WWDC2018大会上面 苹果指明了OpenGL将在Mac OS10.14弃用, OpenGL ES将在iOS上弃用,虽然现在API还能访问,但是已经被标记了弃用的API很有可能在未来的某一刻被抹去
OpenGL: 不支持多线程操作, 不支持异步处理, OpenGL 本身设计上存在的问题已经影响了 GPU 真正性能的发挥
Metal 简化了 CPU 参与渲染的步骤,尽可能地让 GPU 去控制资源。与此同时,拥有更现代的设计,使操作处于可控,结果可预测的状态。在优化设计的同时,它仍然是一个直接访问硬件的框架。与 OpenGL 相比,它更加接近于 GPU,以获得更好的性能。
在 MacOS 10.14 的文档中,苹果表示使用 OpenGL 和 OpenCL 构建的应用还可以继续在 macOS 10.14 中运行(但是即使 macOS 支持 OpenGL ,其内置版本依然是 8 年前发布的 OpenGL 3.3 ,而不是去年发布的 4.6)。苹果表示,这些“遗留技术”并不推荐使用。 很明显,苹果此举是想要大力推广 Metal 图形技术,来替换掉“古老”的 OpenGL 接口和 OpenCL 接口。
大家复盘了一下,发现我们团队的图像处理方面的知识储备基本上是0,基于这个考虑我们选择了metal, 因为metal比OpenGL有优势
针对GPUImage存在的弊端以及限制, 制定了metal替代的方案以及需要达到目标
1.metal实现基本的图片渲染和处理 2.metal实现复杂的动效以及特效处理 3.封装一个完整的组件,使得渲染业务和UI业务解耦 4.为了更好的扩展,制定了一套渲染规则和参数,行程json文件,方便为了渲染方式的扩充, 可以服务器下发也支持本地配置
针对耗电发热问题,通过review代码发现了2个问题
1.渲染的过程效果需要保存到本地, 沿用了之前的模式, 通过使用CoreGraphics对渲染结果显示的view多次截图的方式,最后将一张纸图片合成MP4文件
2.渲染效果没有做缓存,也就是每次点击的时候需要重复进行渲染
解决方案:
1.用渲染生成的纹理替代截图的方式
2.对渲染的效果进行缓存
这里有一个问题, 怎么将纹理转为渲染过程的mp4? 怎么做缓存?
纹理转CVPixelBuffer写入本地的方案研究:
ReplayKit:
API:提供开始录制方法和结束录制
问题:
1.当开始录制方法被调用时用户会收到权限请求警告,这个警告每次会在开始录制时出现。然而,一旦用户选择了其中一种偏好设置,系统会在接下来的8分钟记住这个选择。
2.会录制整个屏幕,而我们的渲染效果显示的只是屏幕中间的一小个view, 会导致多余的内容被录制上
那怎么办呢?能不能从视频播放哪里找到一点启发呢?
想到之前做播放器的时候用了MPMoviePlayerController,但是因为他是苹果高度封装的类,对我们使用很方便,可自定义性越受限制,所以就做一个属于我们的播放器吧,AVAssetReader
可以从原始数据里获取解码后的音视频数据,结合AVAssetReaderTrackOutput
,能读取一帧帧的CMSampleBufferRef
。CMSampleBufferRef
可以转化成CGImageRef
。 这是视频的读取流程, 那是不是应该有一个将这个流程反过来的类呢?
带着这些疑问, 又重新找了找苹果的API, 最后找到了AVAssetWriter这个类, 可以将CVPixelBuffer类型的数据,直接写入到指定的文件, 怎么将metal渲染的纹理数据MTLTexture 转换为CVPixelBuffer? 苹果是提供了两者相互转换的方法
显示方式:
渲染完再输出显示, 还是边渲染边显示?
1.渲染完再显示,因为一整个渲染过程已经结束
1.优点:
- 渲染流程只用一次, 结果显示的时候可以取本地保存的过程视频直接播放
- 可以减少GPU的资源消耗,节省电量,改善发热的问题
- 可以不用限制用户对保存按钮的点击, 用户可以直接点击保存, 然后转存到相册 2.缺点需要耗费少量时间等待渲染完成再显示
2.边渲染边写入:
1.用户可以不用等待
2.缺点是如果用户一直停留在同一个页面上,会重复进行渲染,因为有些特效是循环的效果
两种方案都支持了, 做了线上的灰度测试, 发现渲染完在显示的方案, 需要等待的时间,95%以上线上用户在0.5-1.5s时间,所以我们考虑采用渲染完再显示的方案, 这样做的优点是可以节省更多电量
重构具体实现:
1.因为顶点函数和片元函数与渲染的结果绑定很紧密,所以我们近把目前支持的.metal文件保留, 特定功能需要自己实现顶点函数和片元函数
2.使用类簇的设计模式,将对外的接口封装在一个父类上面,接受一个mtkView(也就是我们渲染效果需要显示的view),返回一个实例对象
- 1.初始化方法
- 2.设置顶点坐标和纹理坐标的方法
- 3.设置纹理数据的方法
- 4.设置渲染管道的方法(内部依赖设置顶点函数和片元函数)
- 5.通过提前订好的协议json文件, 来决定片元函数和顶点函数需要的其他参数(用于生成不同的图像处理算法)
- 6.创建命令缓冲区, 并将需要的命令添加到缓冲区
- 7.通过一个block将命令返回给客户端commondBuffer
- 8.客户端执行commondBuffer就可以执行渲染操作了
- 9.里面还实现了一些其他方法:比如支持直接将纹理写入本地沙盒路径,纹理转图片等操作
3.每一种渲染操作都会提供一个子类出去,如果需要定制的话,客户端只需要在子类里面重写对应的方法,加入自己的操作就行
4.如果需要新的渲染特效,只需要重新定义一个子类,重写方法实现