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


这么复杂的项目我打算从以下几个方向来依次阐述它的核心技术:
硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?
文章图片

文章图片
GPU 硬件加速能力的抽象
硬件加速之所以快 , 在于运算单元多 , 并行能力强 。同样也带来一些限制 , 参与运算的数据必须符合并行计算的要求 。下图描述了大致的流程 。
硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?
文章图片

文章图片
渲染抽象层的设计
目前消费电子设备基本都配备了硬件显卡 , 但是很不凑巧主流设备中的显卡驱动存在较大的差异 。因此想要构建完善的硬件加速渲染器 , 对不同厂商的 GPU 驱动做一层抽象是非常有必要 。
硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?
文章图片

文章图片
这里叫 RAL(Render Adapter Layer)这只是一个名称 。在游戏引擎行业中大家更习惯于叫它RHI(RHI 一般还涉及线程和异步的相关策略) , 详细可以参考 Unreal 的设计 。
那么 RAL 到底涉及哪些东西?这就要说到显卡的差异了 , 就目前来说主流的图形技术在所有显卡中都是相通的 。显卡核心组成部件都类似(高级显卡存在一些新的着色器流程 , 暂时我们不会用到) 。只是驱动在所在的平台存在差异 , 也就是显卡功能性的描述接口存在差异 。这部分差异主要存在于 2 个方面(当然还存在一些细微细节不一样 , 比如窗口坐标系和 NDC 的差异 , 纹理采样坐标系的差异) 。
硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?
文章图片

文章图片
其中 API 的差异可以通过对驱动接口的包装来抹平(有点繁琐) , 编程语言就相对来说非常麻烦了 。Skia 内部内置了自己的一套显卡编程语言叫 SKSL , 可惜文档比较少 。为了达到缩减包体积的效果 , 设计了一套自己的编程语言 。我管它叫 RSL 。
设计一套新的 Shader 编程语言
为什么要设计一套新的编程语言和语法?为什么不直接使用 glsl 的语法?
这里有 2 方面的思考(主要为了方便我实现编译器或者叫转化器):
在这之前我尝试让 OpenglES 运行在 iOS 的 Metal 之上(小游戏引擎的内核项目) , 手写过 glsl 的编译器 。用来转化到 Metal 的 MSL 语法之上 。由于 glsl 的 Spec 文档有点多而且复杂 , 为了测试编译器的稳定性 , 抓取了 ShaderToy(一个交流 webgl shader 的网站)1w5 千个左右的 shader 进行测试 。语法分析通过率只有 95% 多点 , 总有一些我没有考虑到语法 。所以说还是不太稳定 , 工作量有点大 。
glsl 规范比较老 , 缺乏语义的支持 。
应该还有其他的理由 , 比如我自己设计的语法 。但凡有不太容易实现的部分 , 我可以选择剔除掉 。
硬核万字长文:我是如何把Skia的体积“缩小”到1/8的?
文章图片

文章图片
为了缩小包体积 , 我尝试把 Shader 的编译拆分成离线和在线两个端 。和大多编译器的项目类似 , 或许他们都管这叫编译前端和后端 。
离线端:编译源码 , 抽取语义得到 AST(抽象语法树) , 由于这部分离线运行 , 可以任意选择开源项目配合实现 , 不用在乎包体积 。现在开源的编译器项目很多 , 比如 Flex 和 Bison 很容易就可以构建出离线端 。理论上应该对 AST 进行优化操作 , 奈何我本人对优化算法知之甚少 。所以目前还没有实现任何优化相关的部分 。最后对 AST 进行序列化 , 也可以稍微做点处理 , 一切以方便设备端翻译工作为目的 , 然后就可以内嵌或者动态下发到目标端上面了 。