硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?( 七 )


这是由于为了适应现代 GPU 的运算模式 , 不得不在提交 GPU 之前做很多预处理 。包括但不限于 “三角化” “特殊的边缘抗锯齿算法” 等等 , 但是在软渲染的流程中则简单的多 。
显卡尽管可以比 CPU 更快速的处理像素 , 但是像素的成本处理在整个过程中占比不高 。随着显卡速度越来越快、屏幕分辨率越来越高、显卡的驱动标准进一步提升 , 这些问题得到了反转 。目前硬件加速矢量渲染已经作为重要的优化手段来使软件界面更加流畅 。
裁剪
此裁剪和几何部分的多边形裁剪并不一样 。特定场景下渲染器需要对渲染的结果做一些限定 , 比如上层的渲染逻辑只希望部分绘制的结果被用户看到 。就像 Android 中父 View 限定子 View 的绘制不能超过父亲指定的区域一样 。
硬件提供的裁剪
几乎所有的显卡都提供了 scissor 的能力 。我们在渲染前给显卡前设置一个矩形区域 , 如果有像素超过这个窗口就会被显卡丢弃掉 。
但是显卡自带的裁剪能力要求裁剪的区域必须是一个矩形 , 并且这个矩形还不能够旋转 。如果要裁剪一个奇异形状就无能无力了 , 这极大限制它的使用场景 。但是由硬件直接提供的能力性能非常好 , 对渲染无侵入 。
ClipPath
Skia 中提供了一个裁剪画布的接口 ClipPath , 它可以把一个贝塞尔曲线围成的区域作为裁剪的区域 。它的功能很强大 , 几乎可以涵盖全部的裁剪需求 , 如果不是性能太差就没有必要提及其他的方式了 。
如果需要通过 ClipPath 来实现对画布的裁剪 , 需要先构建一个和画布一样尺寸的掩码图 。然后把区域绘制到掩码图上 , 在后续的绘制过程中要逐像素采样掩码图来判断要不要剔除 。当然这个过程非常的繁重 , 体现在三个方面:
需要对区域做预处理 , 甚至需要做堆叠剔除
需要对贝塞尔曲线包围的区域做三角化
需要消耗一次额外的绘制操作
正如前文描述的那样 , 复杂曲线围成的区域处理起来都非常复杂而且慢 。
更快的数据结构
为了解决或者说部分解决 贝塞尔曲线的复杂度带来的性能损耗 。可以使用多个矩形来表示一个复杂区域 , 但是要求矩形之间不能存在堆叠 。下图描述了如何剔除矩形之间的堆叠 , 只需要执行一次线扫描算法即可 。
硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?
文章图片

文章图片
同样矩形非常容易就可以剖分成三角形 , 并不需要使用复杂的三角剖分的算法 。所以可以快速构建对二维区域的描述 。同样基于“矩形集合”的二维区域描述非常容易构建出并交差等运算 。而且相关的碰撞检测算法也非常容易实现 , 但是对于需要使用曲线包围的区域就显的比较乏力了 。
Skia 中使用 SkRegion 这个数据结构来对这个算法进行描述 。
SDF 快速剔除
SDF(Signed Distance Field , 有向距离场) , 这里用了一种模拟 SDF 的方法来进行快速的裁剪 。它发生在光栅化后像素处理的最后阶段 。
硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?
文章图片

文章图片
比如上图中像素 P 和像素 Q , 如果需要保留多边形 (A , B , C , D , E)区域 。那么就要找到一个办法来区分像素 P 和 Q 谁落在多边形内 , 谁落在多边形外 。这不是一个很麻烦的事 。
硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?
文章图片

文章图片
如上图所示 , 从多边形任意一个点进行“行进” , 图中从 C 点开始 。那么向量 CB 需要逆时针旋转才可能和向量 CP 重合 , 也就是 P 点在向量 CB 的左侧 , 相反像素 Q 在向量 CB 的右侧 。