资源文件的转换问题
我们上周在游戏引擎上面的工作中遇到一些 bug ,涉及到过去的一些设计问题。维持讨论了几天解决该问题的方案。今天终于把最终方案确定了下来,值得做一个记录。
bug 出在游戏资源文件的转换上面。
游戏里用到的资源通常需要一个导入资源库的过程,例如你的原始贴图是一个 png 文件,但是引擎需要的是对应运行平台的压缩格式,windows 上是 dxt ,手机上是 ktx 等等。这个过程,在 Unity 等商业引擎中,是放在资源导入流程中。
我们的引擎把这个转换过程放在虚拟文件系统这个层次。这个设计决策是因为,我感觉统一导入资源是个痛点,用的人通常需要等待导入过程。Unity 用了 cache server 来解决这个痛点,但我认为 cache server 也存在一些设计问题 ,这个会在后面再展开一次。
我更希望转换过程是惰性的,直到最终运行需要的资源才需要转换。
在我们的设计中,所有需要转换的资源,都有一个后缀为 .lk 的同名文件放在文件系统中。它描述了怎样加工原始素材,它的作用和 Unity 的 .meta 文件基本一致。
我们的虚拟文件系统在发现一个文件有 .lk 时,会在请求该文件的时候调用构建模块转换源文件。
我们一开始假设的前提是,一个源文件加对应的 lk 文件,在加平台参数,三者的内容就决定了最终生成的文件是什么。所以前三者的 hash 就能用于转换过程的 cache 。最近发现,这个前提是不成立的,导致了 bug 的产生。
原因是:对于 shader 文件,它其实是一种代码,类似 c/c++ 代码。编译一个 shader 其实是依赖很多文件的。所以光有一个 shader 源文件无法准确的 cache 结果:例如,我们修改了 shader 中 include 的另一个文件,但是 fileserver 并不知道,虚拟文件系统返回了 cache 结果,而没有重新编译。
一开始想解决这个问题时,我想放弃惰性构建这个机制。即,把资源转换和 fileserver 分离。这样,修改了源文件,就由资源转换模块去构建这个资源文件。客户端永远认为在运行时资源已经是构建好的(和 Unity 一致)。
这个方案最为简单,但在组内讨论的时候很快被否决了。因为这样又倒退回去了,并没有解决原先想解决的痛点。经过几个方案的讨论,我们最终找到了比较合理的方法。
以 shader 为例,假设有一个 shader 文件叫 a.sc ,有一个 a.sc.lk 指明了 sc 该如何构建。我们的 fieserver 在收到 build 请求的时候,会无条件的重新编译 a.sc 在指定平台上的结果,并把结果文件的 hash 返回。这一步是不做任何 cache 的。
但是,新的方案中,如果你请求了 a.sc 在 ios 上的版本,那么,构建模块会在虚拟文件系统中添加一个叫做 a.sc.lk.ios 的构建脚本文件,详细记录了 a.sc 在 ios 上的构建方法,和构建过程中的依赖关系,包括依赖文件的路径,和当前这些依赖文件的 hash 。
那么,这个 a.sc.lk.ios 文件,其实就唯一确定了一个编译好的目标文件。因为任何一个依赖文件的修改,都会导致文件内容的变更(hash 值变了)。
这个文件在当此运行会话中,对 fileserver 的客户端是不可见的。这是因为我们假定运行过程中,所有文件在一颗 merkle tree 上,是不可变更的。但是新的会话就能看见这个新增的 .ios 文件了。
一旦客户端看得到 .ios 文件,它就可以用这个 .ios 文件的 hash 去请求编译的结果。由于每个 .ios 文件的内容都能唯一确定一个编译结果,这样,fileserver 就能对结果做 cache 。
.ios 文件不是资源编译的结果,而是编译的参数。它在数据仓库里就可以有多份,而编译的结果只需要保存最终的一份。大多数情况下,用户只会请求最新的一份编译结果,但万一用户请求过期的版本,fileserver 也可以重新生成出来。
这有点像 github 的大文件储存方案,不在 git 主仓库里保存大的二进制文件,而保存了一个唯一的 url ,把文件放在了另一个服务中。在这里,这另一个服务就是编译资源的模块。
这个机制做到了惰性编译资源,又可以合理的 cache 资源的编译结果。和 Unity 的 cache server 不同,它用来索引 cache 的 key 其实是编译资源的完整过程,包括了编译的依赖关系。
所以,编译模块,完全可以跨项目的 cache 这个过程。即,如果你一张贴图用在一个项目中,被编译过一次;当你把这张贴图复制到新项目使用时,是不需要重新编译的。而 Unity 中同一张贴图即使在同一个项目中换个位置,都会导致 guid 变化,从而让 meta 文件变化,致使资源重新编译。
文件服务还知道所有的需求,所以它完全有能力在你请求 ios 版本的同时,预期你还会在以后请求 andriod 版本。所以 fileserver 还有能力利用闲置时间去提前生成那些尚未请求的版本。而 Unity 的 cache server 则是一个纯粹的 key / value 服务,完全不可能做到这些。
建议继续学习:
- 如何查找消耗资源较大的SQL (阅读:13430)
- HTML5和CSS3工具资源汇总 (阅读:3658)
- UI设计师的盛宴:Web UI设计资源大系 (阅读:3023)
- 分享一些可视信息设计资源 (阅读:2677)
- 突破systemtap脚本对资源使用的限制 (阅读:2513)
- JavaScript 全半角转换 (阅读:1696)
- 游戏引擎中的资源生命期管理问题 (阅读:1030)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:云风的 BLOG 来源: 云风的 BLOG
- 标签: 资源 转换
- 发布时间:2020-02-01 16:44:33
- [67] Go Reflect 性能
- [67] Oracle MTS模式下 进程地址与会话信
- [67] 如何拿下简短的域名
- [61] IOS安全–浅谈关于IOS加固的几种方法
- [60] 图书馆的世界纪录
- [59] 【社会化设计】自我(self)部分――欢迎区
- [58] android 开发入门
- [56] 视觉调整-设计师 vs. 逻辑
- [49] 给自己的字体课(一)——英文字体基础
- [47] 界面设计速成