搞 Unity 性能必须要知道的 Gfx.WaitFor 点位常识适用版本Unity 2018 | 工具ProfilerEditor / Development Build核心用途看懂 Profiler 里的一堆 WaitForxxx快速定位 CPU 瓶颈还是 GPU 瓶颈目录前言为什么要搞懂这些 WaitFor先搞清楚三个角色Gfx.WaitForPresent —— 最常见的那个Gfx.WaitForPresentOnGfxThread —— 渲染线程版Graphics.PresentAndSync —— 没开多线程渲染时Gfx.WaitForCommands —— 和上面反过来Gfx.WaitForGfxThread —— 等渲染线程干活GfxDeviceXXX.WaitForLastPresent —— 排队等翻屏WaitForTargetFPS —— 真正在摸鱼的那个实战拿到一份 Profiler 数据怎么判一张图速查表总结一、前言为什么要搞懂这些 WaitFor用 Unity Profiler 看帧耗时你一定会看到类似这样的几个 MarkersGfx.WaitForPresent Gfx.WaitForPresentOnGfxThread Gfx.WaitForCommands Gfx.WaitForGfxThread Graphics.PresentAndSync WaitForTargetFPS很多人一看 “Wait” 就慌了觉得是哪里在 “等”、是性能问题。其实不一定 ——有些 Wait 代表 GPU 瓶颈有些代表 CPU 瓶颈还有的纯粹是 CPU 在摸鱼等帧率。这篇教程的目的就是让你一眼看懂每个 WaitFor 的含义、出现位置、瓶颈方向拿到 Profiler 能立刻判断该优化 CPU 还是 GPU。二、先搞清楚三个角色理解 WaitFor 之前先记住 Unity 渲染管线的三个执行者角色线程干什么主线程Main Thread1 个你的 Update、逻辑、物理、动画、发渲染命令渲染线程Render Thread1 个把主线程发的命令转成 GPU 能懂的 API 调用GPU无数核真正画像素的它们的关系是流水线主线程 ──发命令──→ 渲染线程 ──调用API──→ GPU三个环节谁慢另外两个就得等。WaitForxxx 就是 “等” 的那段时间的采样名称。三、Gfx.WaitForPresent —— 最常见的那个基本信息项目内容全称Gfx.WaitForPresent出现线程主线程Main Thread出现条件开启了多线程渲染默认就是开的频率几乎每帧都有发布包中尤其常见含义主线程已经把这一帧的所有渲染指令发出去了没有更多事要干了正在等 GPU 把这帧画完并翻到屏幕Present。可以理解为主线程“GPU活交代完了你慢慢画画完了叫我。”主线程就靠在墙上等着。瓶颈判断情况结论Gfx.WaitForPresent 占满帧耗时比如 33ms 帧里占了 20msGPU 瓶颈✅Gfx.WaitForPresent 很低或为 0GPU 不慢问题在主线程为什么它是 GPU 瓶颈因为主线程已经把所有活干完了发完命令了剩下的时间全在等 GPU 出结果。如果 GPU 画得快WaitForPresent 就短GPU 画得慢WaitForPresent 就长。什么会导致它变高渲染分辨率太高面数三角形太多贴图太大、采样次数太多Shader 复杂特别是 Fragment Shader 重后处理特效重Bloom、SSAO、HDR、DOF 等Overdraw 高半透明重叠多光照计算复杂实时光、阴影四、Gfx.WaitForPresentOnGfxThread —— 渲染线程版基本信息项目内容全称Gfx.WaitForPresentOnGfxThread出现线程渲染线程Render Thread出现条件开启多线程渲染含义它是 WaitForPresent 的 “渲染线程版本”。渲染线程在 Present 时等待 GPU 完成当前帧的渲染。瓶颈判断情况结论WaitForPresentOnGfxThread 很高GPU 瓶颈✅本质上和 WaitForPresent 是同一回事只是采样的线程不同。两个同时高 确认 GPU 瓶颈。五、Graphics.PresentAndSync —— 没开多线程渲染时基本信息项目内容全称Graphics.PresentAndSync出现线程主线程出现条件关闭多线程渲染Player Settings 里去掉 Multithreaded Rendering含义没开多线程渲染时主线程自己干渲染线程的活自己做 Present 等 VSync。和 WaitForPresent 的关系多线程渲染 ON → Gfx.WaitForPresent主线程等渲染线程 GPU 多线程渲染 OFF → Graphics.PresentAndSync主线程自己等 GPU瓶颈判断情况结论PresentAndSync 很高GPU 瓶颈✅六、Gfx.WaitForCommands —— 和上面反过来基本信息项目内容全称Gfx.WaitForCommands出现线程渲染线程Render Thread出现条件多线程渲染开启时含义渲染线程没事干在等主线程发新的渲染命令过来。渲染线程主线程你快点啊我活干完了下一批命令呢 渲染线程干等着。瓶颈判断情况结论Gfx.WaitForCommands 很高CPU主线程瓶颈✅为什么它是 CPU 瓶颈这说明 GPU 和渲染线程都闲着但主线程太慢 —— Update 逻辑太重、物理计算太多、或者某些同步操作卡住了主线程导致渲染命令送不过来。什么会导致它变高复杂的 C# 逻辑Update、协程、Invoke物理引擎开销大量 Collider、Rigidbody动画系统开销大量 Animator粒子系统过多UI 重建Canvas rebuildGC 触发GC.Collect资源加载同步 LoadAsset自定义花哨算法在主线程跑七、Gfx.WaitForGfxThread —— 等渲染线程干活基本信息项目内容全称Gfx.WaitForGfxThread出现线程主线程出现条件多线程渲染开启时含义主线程等渲染线程把上一批命令处理完才能继续发下一批。主线程渲染线程你上一批处理完没没完我先等着。瓶颈判断重要这是最容易搞混的一个记牢了情况结论WaitForGfxThread 很高比如 8ms渲染线程慢 → 通常是 GPU 瓶颈也可能是渲染线程被某件事卡住WaitForGfxThread ≈ 0主线程不等渲染线程 → GPU 瓶颈✅为什么 WaitForGfxThread ≈ 0 反而是 GPU 瓶颈逻辑链主线程发完命令 → 渲染线程也很快处理完 → 主线程回来 Check 发现不用等 → 然后主线程进入 WaitForPresent 开始等 GPU。所以 WaitForGfxThread ≈ 0 WaitForPresent 很高 铁板钉钉的 GPU 瓶颈。快速记忆口诀WaitForGfxThread 高 → 渲染线程堵了可能是 GPU 也可能是渲染线程自身 WaitForGfxThread 低 → 主线程不卡渲染线程那就是 GPU 画不过来八、GfxDeviceXXX.WaitForLastPresent —— 排队等翻屏基本信息项目内容全称例如 GfxDeviceD3D11.WaitForLastPresent、GfxDeviceVulkan.WaitForLastPresent出现线程主线程出现条件GPU 命令队列堆积含义Unity 允许 CPU 比 GPU 快几帧QualitySettings.maxQueuedFrames默认 2~3 帧。当 GPU 跟不上时队列满了CPU 想继续塞命令但塞不进去只能等 GPU 先把前面的处理完。CPUGPU 你前面的还没画完队列满了我等你画完一帧再塞新的。瓶颈判断情况结论WaitForLastPresent 很高GPU 吞吐不足队列堆积→GPU 瓶颈优化手段降低maxQueuedFrames减少延迟但不解决瓶颈本质还是要优化 GPU 渲染压力九、WaitForTargetFPS —— 真正在摸鱼的那个基本信息项目内容全称WaitForTargetFPS出现线程主线程出现条件Application.targetFrameRate 或 VSync 开启了含义CPU 太快了一帧的活干完了还没到目标帧率的时间点主动空转等待。CPU活干完了但目标 60fps现在才过了 8ms我再等 8ms 再开始下一帧。瓶颈判断情况结论WaitForTargetFPS 很高不是瓶颈是正常的帧率控制WaitForTargetFPS 为 0CPU 已经满负荷跑达不到目标帧率什么时候需要关注它基本不需要关注。如果一帧总耗时是 16ms60fps其中 WaitForTargetFPS 占了 8ms说明实际干活只用了 8ms性能很宽裕。唯一要注意的是如果你取消了帧率限制但 WaitForTargetFPS 依然很高检查 VSync 是不是还开着。十、实战拿到一份 Profiler 数据怎么判场景一Gfx.WaitForPresent 占大头Main Thread: PlayerLoop (22ms) ├── Update (2ms) ├── Physics (1ms) ├── Rendering (3ms) └── Gfx.WaitForPresent (16ms) ← 这个最大判断GPU 瓶颈。主线程总共 22ms16ms 在干等 GPU。方向优化渲染 —— 减面、减贴图、减 Shader 复杂度、减后处理、合批降低 DrawCall、LOD。场景二Gfx.WaitForCommands 占大头Render Thread: Gfx.WaitForCommands (12ms) ← 这个最大 Gfx.ProcessCommands (2ms) Gfx.PresentFrame (1ms)Main Thread: PlayerLoop (20ms) ├── Update (8ms) ← 很重 ├── Physics (4ms) ← 很重 ├── Animation (3ms) ← 很重 └── Rendering (3ms)判断CPU主线程瓶颈。渲染线程 12ms 在等主线程发命令过来。方向优化 CPU —— 减 Update 逻辑、减物理、减 Animator、减粒子、异步加载、Profile 具体哪个 System 最耗时。场景三WaitForGfxThread ≈ 0 WaitForPresent 高Main Thread: PlayerLoop (30ms) ├── PlayerUpdate (2ms) ├── Gfx.WaitForGfxThread (0.1ms) ← 几乎为 0 ├── Gfx.WaitForPresent (18ms) ← 很高 └── ...判断GPU 瓶颈。主线程发命令快、渲染线程处理快但 GPU 画不完。方向同上优化 GPU 渲染压力。场景四WaitForGfxThread 很高Main Thread: PlayerLoop (35ms) ├── PlayerUpdate (2ms) ├── Gfx.WaitForGfxThread (15ms) ← 很高 ├── Gfx.WaitForPresent (5ms) └── ...判断渲染线程卡住了 —— 可能是 GPU 压力大也可能是渲染线程被某件事阻塞比如驱动调用耗时、Shader 编译、资源上传。方向点开 Render Thread 看具体什么操作耗时如果也是 WaitForPresent ≈ GPU 瓶颈如果是 ProcessCommands 某一步耗时异常 → 查具体渲染操作为什么慢。场景五WaitForTargetFPS 占了大半Main Thread: PlayerLoop (16ms) ├── PlayerUpdate (3ms) ├── Rendering (2ms) ├── WaitForTargetFPS (10ms) ← 很大 └── ...判断没有性能问题。CPU 和 GPU 都很快在等目标帧率的时间点。方向不用优化。如果想跑更高帧率提高 Application.targetFrameRate 或关 VSync。场景六混合型 —— 两个都不低Main Thread: WaitForPresent (8ms) Render Thread: WaitForCommands (6ms)判断CPU 和 GPU 都有压力。两者互相等。方向两头优化 —— 先看主线程哪块最重CPU 瓶颈部分再看渲染压力GPU 瓶颈部分挑收益最大的先改。十一、一张图速查表Profiler Marker出现位置含义瓶颈方向Gfx.WaitForPresent主线程主线程等 GPU 画完 PresentGPU 瓶颈Gfx.WaitForPresentOnGfxThread渲染线程渲染线程等 GPU 画完GPU 瓶颈Graphics.PresentAndSync主线程关多线程渲染时的 Present VSyncGPU 瓶颈Gfx.WaitForCommands渲染线程渲染线程等主线程发命令CPU主线程瓶颈Gfx.WaitForGfxThread高主线程主线程等渲染线程处理完渲染线程卡 /GPU 瓶颈Gfx.WaitForGfxThread≈ 0主线程主线程不等渲染线程GPU 瓶颈GfxDevice.WaitForLastPresent*主线程命令队列满等 GPU 消费GPU 瓶颈WaitForTargetFPS主线程CPU 太快空转等帧率无瓶颈✅最简版记忆口诀WaitForCommands 高 → CPU 太慢优化主线程 WaitForPresent 高 → GPU 太慢优化渲染 WaitForGfxThread ≈ 0 → GPU 太慢同上 WaitForTargetFPS 高 → 没事机器很好十二、总结判断流程拿到 Profiler看 Main Thread 耗时 │ ├── WaitForPresent 占大头 │ └── ✅ GPU 瓶颈 → 优化渲染面数、贴图、Shader、后处理 │ ├── WaitForCommands 占大头 │ └── ✅ CPU 瓶颈 → 优化主线程逻辑、物理、动画、GC │ ├── WaitForTargetFPS 占大头 │ └── ✅ 没事性能够 → 想跑更高帧率就提帧率限制 │ ├── WaitForGfxThread 很高 │ └── 点开 Render Thread 看 │ ├── 也在等 Present → GPU 瓶颈 │ └── 某步操作异常耗时 → 查具体渲染操作 │ └── 混合 └── 两头都优化挑收益大的先改最关键的一句话Gfx.WaitForPresent 高 GPU 瓶颈Gfx.WaitForCommands 高 CPU 瓶颈。记住这两个就能解决 90% 的性能定位场景。