Unity对Mesh/Texture资源的上传GPU机制,allowSceneActivation和ResetPreMappedBufferMemory接口作用分享

1 资源是如何上传到GPU的

比起干说,还是结合Unity Profiler做实验来得直观和有说服力。

首先准备一个带Mesh的Prefab,保证从磁盘加载到CPU内存之前游戏场景中没有其他实例在使用该Prefab所指向的Mesh资源,然后在运行过程的某个时刻开始,先后调用Load和Unload逻辑,通过Unity的Profiler观察GPU内存总量变化和上传数据变化。

public class TestUpload : MonoBehaviour
{
    private List<GameObject> tps = new List<GameObject>();

    void Start()
    {
        StartCoroutine(CountDown());
    }

    IEnumerator CountDown()
    {
        yield return new WaitForSeconds(0.2f);
        JustLoad("Assets/test.prefab");
        yield return new WaitForSeconds(0.2f);
        ReleaseAsset(0);
    }

    private void JustLoad(string path)
    {
        var go = AssetDatabase.LoadAssetAtPath<GameObject>(path); //只加载,甚至不激活和显示
        tps.Add(go);
    }

    private void ReleaseAsset(int idx)
    {
        tps[idx] = null;
        EditorUtility.UnloadUnusedAssetsImmediate(); //使用此接口及时触发GPU端资源释放
    }

    private void OnDestroy()
    {
        tps.Clear();
    }
}

下图来自Profiler:

在Unity运行期间,加载资源(Mesh或Texture)完成的同时,无论当前帧是否有渲染目标Mesh或者使用目标Texture的需要(即便只是加载了资源,之后什么都不操作)都会触发向GPU上传数据的操作,并被保存在名叫GfxBuffer的内部类中。传输模式在Editor模式下是同步的,会在触发上传的同一帧内完成向GPU提交全部数据(Header和Binary)。 同样,当Unity不再持有该Mesh资源的引用,并且在寻求主动释放GPU资源时,显存中的关联资源(Vertex/Index Buffer等)才会被释放,在Editor模式下,这个操作对应了EditorUtility.UnloadUnusedAssetsImmediate()方法。

为了知道Unity在上传GPU资源数据时到底干了什么,这边比较了一下上传关键帧和其他时间CPU部分负载的异同,利用Profiler自带Hierarchy查找关键词Mesh,比较异同后发现:处于上传负载的关键帧时期,Unity触发了2个独特的函数调用:

  1. Mesh.AwakeFromLoad
  2. Mesh.CreateMesh

这两个方法在Prolier中的具体执行层级如下图所示:

可见当读取磁盘数据的回调一旦完成,就触发了Mesh.AwakeFromLoad,进而触发了Mesh.CreateMesh,而这个方法内部主要负责将Mesh中的"Vertice"和"Index"数据依序通过GeometryBuffer上传到GPU端。

进一步梳理下Mesh和Texture等资源的处理流程,可以区分为两种方式:

  1. 与场景同时加载
    • 其本质也是从磁盘加载,但是随同场景出现而出现
    • 在调用Profiler测试时,往往因为场景早于Profiler工作而出现,所以一部分基于场景的Mesh和Texture早已在GPU了
  2. 运行时由代码触发的从磁盘加载
    • 这里如果是从构建好的AssetBundle中获取数据,那么就有2中不同的上传模式:
      1. Sync:
        • 该模式在资源build时期(就是打AssetBundle时期)会将数据的Header和Binary全部打包到.res文件内,
        • 在游戏运行时,Unity从磁盘(Bundle)中读取这个文件到内存,之后会由主线程于一帧内将资源(Header和Binary)Upload到GPU。
      2. Async
        • 还是在资源build时期,会将Header写入.res文件中,Binary数据则写入.resS文件中
        • 在游戏运行时,Unity从磁盘(Bundle)中读取.res文件到内存,解析出Header数据,之后Unity采用streams的方式从.resS文件中加载Binary数据到GPU,这个过程使用了一个固定大小的Ring Buffer(环形缓冲),而且还会利用多线程,分多帧处理。
    • 如果是从Resources目录读取资源,或者在Editor模式下调用AssetDatabase获取资源,那么统一走Sync模式

备注1 Unity重复使用一段环形缓冲作为流式(Streaming)上传数据到GPU的区域,这么做的主要目的是避免重复开辟新的内存。在ProjectSettings->Quality->AsyncAssetUpload->BufferSize可以控制环形缓冲的大小,默认是4MB,最小可调到2MB,最大则是2GB。当单个Mesh或Texture的尺寸超过环形缓冲大小时,Unity不得不重新开辟RingBuffer以适应上传数据大小,出现这种情况会导致效率下降,因此最佳策略是手动调整BufferSize,以满足场景内最大Mesh/Texture的尺寸。

备注2 Unity也提供了控制每帧Upload时间(ms)的接口和设置,可以在ProjectSettings->Quality->AsyncAssetUpload->TimeSlice中调节每一帧最大可占用的CPU时长,这个数值越大,意味着GPU将越快获得Mesh/Texture数据,代价是CPU在提交数据的这段时间内负荷增大。

注意只有在出发C#加载Mesh资源的那一帧(上传关键帧),系统才向GPU上传了一定数量是顶点和索引缓冲数据。那一帧过后,系统恢复“常态”。

部分关键参数名的含义参考如下官方文档:

Vertex Buffer Upload In Frame Count/Bytes The amount of geometry that the CPU uploaded to the GPU in the frame. This represents the vertex/normal/texcoord data. There might already be some geometry on the GPU. This statistic only includes geometry that Unity transfers in a frame.
Index Buffer Upload In Frame Count/Bytes The amount of geometry that the CPU uploaded to the GPU in the frame. This represents the triangle indices data. There might already be some geometry on the GPU. This statistic only includes geometry that Unity transfers in a frame.

2 allowSceneActivation

这个值的作用参考文档即可:AsyncOperation.allowSceneActivation

简单来说,我们可以通过在加载场景前将该变量设置为false,从而控制Unity专心于该场景的异步加载,直到完成度达到90%后(也可以提前,但是不能延后)再通过将allowSceneActivation设置为true从而重启其他处于队列中等待的AsyncOperation,比如Unity官方提到的SceneManager.UnloadSceneAsync

我想说的是,有时候如果没有及时提前触发非场景类的AssetBundle异步加载,那么allowSceneActivation也会将这些Bundle的加载停住(stalled),由于是异步提交的,因此误停其他Bundle加载也很可能是偶发的,不一定在测试的时候必现,需要注意和提前规避。

3 ResetPreMappedBufferMemory

这个借口的作用参考这篇官方文档:ParticleSystem.ResetPreMappedBufferMemory

之所以提及这个接口是因为有项目遇到一个战斗中内存突然暴增的问题,特别是在以高倍速播放战斗画面的情况下,Gfx Memory会有倍增的恐怖效果,而且战斗结束一段时间后爆涨的内存仍然没有明显回落。 导致这个问题的原因是同屏粒子特效过多,使得Unity粒子系统底层申请了较大缓存用来存放Mesh等粒子渲染资源。

很显然Unity底层有专门算法控制额外内存申请量,我们的游戏战斗在高倍速播放过程中累积了大量同屏粒子特效的显示请求,这个请求量显然吓到了Unity。粗暴的解决方法是在内存申请高峰之后,适时的调用ParticleSystem.ResetPreMappedBufferMemory()方法重置这部分额外开辟的内存。

一个疑问是Unity的这项预申请大量内存的优化是否仅针对大内存设备启用,或者会依据可用内存大小自动调整?因为如果缓存值是根据可用内存来的确定的上限的,那么短时间内存占用的爆发也算是一种可控范围内的技术处理,我们无需额外修正,不过后来的测试表明并不是(至少2021版还不是)

分析上图,没有在4G手机上找到明显的安可用比例申请内存大小的证据。

当然,优雅的解决方案是控制任何可能短时间内大量生成粒子特效的情景,这其中包括了高倍速播放战斗,也包括其他诸如同屏多角色释放大量粒子特效,甚至粒子特效的制作本身。

Ref

Manual/ProfilerRendering

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容