HarmonyOS 游戏为什么不卡 GPU,却卡在 RenderThread?
子玥酱掘金 / 知乎 / CSDN / 简书 同名大家好我是子玥酱一名长期深耕在一线的前端程序媛 。曾就职于多家知名互联网大厂目前在某国企负责前端软件研发相关工作主要聚焦于业务型系统的工程化建设与长期维护。我持续输出和沉淀前端领域的实战经验日常关注并分享的技术方向包括前端工程化、小程序、React / RN、Flutter、跨端方案在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。技术方向前端 / 跨端 / 小程序 / 移动端工程化内容平台掘金、知乎、CSDN、简书创作特点实战导向、源码拆解、少空谈多落地文章状态长期稳定更新大量原创输出我的内容主要围绕前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读展开。文章不会停留在“API 怎么用”而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍希望能帮你在实际工作中少走弯路。子玥酱 · 前端成长记录官 ✨ 如果你正在做前端或准备长期走前端这条路 关注我第一时间获取前端行业趋势与实践总结 可领取11 类前端进阶学习资源工程化 / 框架 / 跨端 / 面试 / 架构 一起把技术学“明白”也用“到位”持续写作持续进阶。愿我们都能在代码和生活里走得更稳一点 文章目录引言一、先理解一帧是怎么出来的Game ThreadRenderThreadGPU二、为什么 RenderThread 会成为瓶颈三、RenderThread 每帧都在做什么1、Visible Culling2、Render Queue 排序3、Batch4、Command Buffer四、为什么 DrawCall 太多会卡 RenderThread五、UI 为什么特别容易卡 RenderThread六、Particle 为什么容易炸七、真正优秀的游戏都在减少 RenderThread 工作量1、Batch2、Atlas3、Instancing4、缓存静态对象八、为什么现代游戏都在多线程渲染九、HarmonyOS 游戏未来为什么一定会走向 Job System总结引言很多人做 HarmonyOS 游戏优化时都会经历这样一个阶段。打开性能工具发现CPU30% GPU40% 内存正常看起来一切都很健康但是FPS 只有 45继续降低特效关闭阴影 降低分辨率 减少粒子结果GPU 从 40% 降到 20% FPS 还是 45于是很多人开始怀疑人生CPU 不忙GPU 也不忙为什么游戏还是掉帧其实大部分游戏真正卡住的地方既不是 CPU也不是 GPU。而是RenderThread渲染线程。很多大型游戏包括 Unity、UE、Android、HarmonyOS都会存在同样的问题。甚至很多时候CPU 20% GPU 30% RenderThread 100%这才是真正的性能杀手。一、先理解一帧是怎么出来的很多人脑海里的流程CPU ↓ GPU ↓ 屏幕其实真正的流程是Game Thread ↓ RenderThread ↓ GPU Driver ↓ GPU ↓ DisplayGame Thread负责AI 物理 动画 状态更新例如update(){enemySystem.update()physicsSystem.update()}这是逻辑线程。RenderThread负责收集可见对象 排序 Batch 生成 DrawCall 提交 CommandBuffer例如Player Enemy Tree Bullet Particle全部转换成DrawCall再交给 GPU。GPU负责真正绘制Vertex Shader Fragment Shader Texture Rasterization所以CPU ≠ GPU 中间还有 RenderThread很多掉帧都发生在这里。二、为什么 RenderThread 会成为瓶颈因为 GPU 不会直接理解Player Enemy Bullet UIGPU 只能理解CommandBuffer DrawCall Vertex Buffer Texture因此游戏世界Player Enemy Tree Particle必须经过RenderThread转换成GPU Command整个过程类似翻译游戏对象 ↓ RenderThread ↓ GPU语言 ↓ GPU如果翻译速度跟不上GPU 就会等待于是出现GPU 利用率很低 FPS 却很低真正卡住的是RenderThread三、RenderThread 每帧都在做什么一帧里面它其实很忙。1、Visible Culling判断哪些对象可见10000 个对象 ↓ 2000 个可见例如for(objinworld){if(camera.contains(obj)){visibleList.push(obj)}}这是 CPU 运算。2、Render Queue 排序为了减少状态切换会按照Material Texture Shader排序A A A B B B而不是A B A B A否则GPU 状态切换非常昂贵。3、Batch把1000 个 Sprite合并成10 个 DrawCall例如Enemy1 Enemy2 Enemy3 Enemy4DrawCall #1Batch 本身也是 CPU 工作。4、Command Buffer生成DrawIndexed() BindTexture() BindShader()然后提交给 Driver这一部分全部运行在RenderThread四、为什么 DrawCall 太多会卡 RenderThread例如场景中5000 个对象每个对象1 DrawCall那么5000 DrawCallRenderThread 每帧都要排序 状态切换 生成命令60FPS 下每秒30 万次 DrawCallCPU 时间大量消耗在Driver API Call于是RenderThread 100%GPU等待数据利用率只有20%表现出来就是GPU 很闲游戏却很卡。五、UI 为什么特别容易卡 RenderThread这是 HarmonyOS 游戏最容易踩坑的地方例如Column(){ForEach(items)}里面有1000 个节点每次score触发build()导致整个节点树重新布局 重新生成 RenderNode 重新提交最终压力全部来到RenderThread表现CPU正常 GPU正常 FPS下降实际上RenderThread 爆掉六、Particle 为什么容易炸例如屏幕上3000 粒子每个粒子位置 旋转 缩放 透明度都在变化意味着每帧VertexBuffer 更新RenderThreadUpload Buffer 生成 Command 提交 GPUCPU 开销巨大所以很多游戏粒子数量超过5000FPS 会瞬间下降并不是 GPU 不行。而是RenderThread 来不及提交。七、真正优秀的游戏都在减少 RenderThread 工作量核心思想让 RenderThread 少干活。1、Batch错误1000 DrawCall优化20 DrawCall2、Atlas错误100 张 Texture优化1 张大图减少BindTexture()次数。3、Instancing例如1000 棵树错误1000 DrawCall优化1 DrawCallGPU 自己复制RenderThread 几乎不增加负担。4、缓存静态对象例如地图错误每帧重新生成 Mesh优化缓存 VertexBuffer直接复用。八、为什么现代游戏都在多线程渲染因为单 RenderThread 已经不够用了传统GameThread ↓ RenderThread ↓ GPURenderThread 成为唯一瓶颈于是现代引擎开始GameThread ↓ RenderTask1 RenderTask2 RenderTask3 ↓ RenderThread ↓ GPU例如1、Culling 一个线程。2、Batch 一个线程。3、Shadow 一个线程。4、Particle 一个线程。最后RenderThread 负责汇总。结构变成Job System ↓ Worker Thread ↓ RenderThread ↓ GPU这也是Unity DOTS UE5 现代手游越来越快的原因。九、HarmonyOS 游戏未来为什么一定会走向 Job System因为未来越来越复杂Agent NPC 大地图 物理 动画 AI 网络同步全部放在Main Thread一定会卡死未来架构MainThread ↓ Job System AI Thread Physics Thread Animation Thread Render Thread ↓ GPU形成多核 CPU ↓ 并行计算 ↓ 稳定 120FPS这也是未来 HarmonyOS 游戏架构的发展方向。总结很多开发者看到掉帧时第一反应是GPU 不够强。实际上大量游戏真正的瓶颈是RenderThread因为CPU ↓ RenderThread ↓ GPU中间这层负责把游戏世界翻译成 GPU 能理解的语言。最后一句话总结RenderThread 才是 CPU 和 GPU 之间那座最容易堵车的桥。很多时候不是 GPU 画不动。而是RenderThread 根本来不及把数据送过去。