新增Texture Manager功能模块 - 帮助解决游戏项目中纹理资源使用不合理、资源包纹理资源冗余等原因导致的显存消耗问题 - 同一套AssetBundle可以支持多套纹理(每套纹理针对不同平台使用不同的压缩格式)
小游戏有同时运行在手机和桌面设备的需求。使用单一的ASTC压缩格式在桌面端运行时,ASTC格式的纹理需要先解压成对应未压缩的纹理格式然后再上传GPU。解压过程在主线程执行,会带来严重的卡顿问题;上传未压缩的纹理到GPU, 也会导致显存的浪费(显存占用超过4倍)。
TextureManager可以批量生成多种压缩格式的纹理数据,例如ASTC4x4 + ASTC8x8 + DXT。 游戏运行时,TextureManager模块开始初始化,贴图对象会根据以后信息和用户平台设定的纹理格式信息初始化Metadata信息,但是不会分配存储纹理数据。而后当纹理需要加载上传纹理数据时,引擎可以自动根据当前平台、设备性能选择合适的压缩格式进行下载使用。启用TextureManager后,Assetbundle只保留纹理的metadata信息,纹理数据被剥离出去。在Assetbundle打包好后,假如需要调整支持的纹理压缩格式,无需重新打包AssetBundle。
TextureManager支持设置纹理显存用量上限。当显存用量达到预设时,会根据策略计算纹理的分值并排序,优先从GPU上踢出低分值纹理。计算分值时会考虑: - 纹理导入时制定的上传模式(Immediately, Reference, Renderer) - 纹理导入时指定的优先级(Renderer模式) - 纹理Renderer可见性或者被UI引用情况 - 纹理Renderer与相机的距离
之前,在调用 Assetbundle.LoadAsset(Async)、SceneManager.LoadScene(Async)、Resource.Load(Async)等接口加载资产后,引擎会依次 - 1. 创建纹理对象 - 2. 读纹理内容进内存 - 3. 并将纹理内容上传至GPU 调用Assetbundle.Unload(true)、Resource.UnloadUnusedAssets()时,执行 - 从GPU上释放纹理显存 - 销毁纹理对象 为了控制显存占用,需要用户管理好纹理的生命周期(通常是在用户代码中做好引用计数)。在微信小游戏平台频繁调用Resource.UnloadUnusedAssets()会带来严重卡顿问题;使用AssetBundle.Unload(true)对AssetBundle的组织结构要求较高;当bundle中存在多个资源时,难以实现对单个纹理生命周期的控制。
启用TextureManager后,操作序列将变为: - 1. 创建纹理对象 - 2. 下载纹理进缓存(当缓存命中时跳过此步) - 3. 读纹理内容进内存 - 4. 并将纹理内容上传至GPU 其中步骤2~4将延迟至引用纹理的Renderer可见时(或引用纹理的UI被绘制时)再执行。 为避免同时处理大量纹理带来的卡顿与内存峰值,TextureManager会进行分帧平滑调度,将这些操作分配到多帧中。 TextureManager仅对纹理的GPU显存进行管理,不改变纹理对象的生命周期,纹理对象本身仍由原有的逻辑控制。如下图所示,当纹理闲置,即当纹理Renderer不可见或者当前绘制UI对纹理的引用为0时,Texture Manager标记纹理为可卸载,超出显存预算时将从GPU显存中卸载。当纹理重新处于渲染状态时,TextureManager会再次执行步骤2~4,上传纹理内容到GPU用于绘制。
为了解决小游戏中的纹理冗余问题,TextureManager在上传纹理到GPU前会先查看是否已有同样的纹理。发现重复纹理的情况下,会在底层进行映射,使其共用GPU上的同一张纹理,从而避免纹理冗余带来的显存开销。