HarmonyOS 游戏为什么不卡 GPU,却卡在 RenderThread?


子玥酱 (掘金 / 知乎 / CSDN / 简书 同名)

大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩‍💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。

我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,

在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。

技术方向: 前端 / 跨端 / 小程序 / 移动端工程化 内容平台: 掘金、知乎、CSDN、简书 创作特点: 实战导向、源码拆解、少空谈多落地 **文章状态:**长期稳定更新,大量原创输出

我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在"API 怎么用",而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。

子玥酱 · 前端成长记录官 ✨

👋 如果你正在做前端,或准备长期走前端这条路

📚 关注我,第一时间获取前端行业趋势与实践总结

🎁 可领取 11 类前端进阶学习资源 (工程化 / 框架 / 跨端 / 面试 / 架构)

💡 一起把技术学"明白",也用"到位"

持续写作,持续进阶。

愿我们都能在代码和生活里,走得更稳一点 🌱

文章目录

    • 引言
    • 一、先理解一帧是怎么出来的
    • [二、为什么 RenderThread 会成为瓶颈?](#二、为什么 RenderThread 会成为瓶颈?)
    • [三、RenderThread 每帧都在做什么?](#三、RenderThread 每帧都在做什么?)
      • [1、Visible Culling](#1、Visible Culling)
      • [2、Render Queue 排序](#2、Render Queue 排序)
      • 3、Batch
      • [4、Command Buffer](#4、Command Buffer)
    • [四、为什么 DrawCall 太多会卡 RenderThread?](#四、为什么 DrawCall 太多会卡 RenderThread?)
    • [五、UI 为什么特别容易卡 RenderThread?](#五、UI 为什么特别容易卡 RenderThread?)
    • [六、Particle 为什么容易炸?](#六、Particle 为什么容易炸?)
    • [七、真正优秀的游戏都在减少 RenderThread 工作量](#七、真正优秀的游戏都在减少 RenderThread 工作量)
    • 八、为什么现代游戏都在多线程渲染?
    • [九、HarmonyOS 游戏未来为什么一定会走向 Job System?](#九、HarmonyOS 游戏未来为什么一定会走向 Job System?)
    • 总结

引言

很多人做 HarmonyOS 游戏优化时,都会经历这样一个阶段。

打开性能工具发现:

text 复制代码
CPU:30%
GPU:40%
内存正常

看起来一切都很健康,但是:

text 复制代码
FPS 只有 45

继续降低特效:

text 复制代码
关闭阴影
降低分辨率
减少粒子

结果:

text 复制代码
GPU 从 40% 降到 20%
FPS 还是 45

于是很多人开始怀疑人生:

CPU 不忙,GPU 也不忙,为什么游戏还是掉帧?

其实,大部分游戏真正卡住的地方,既不是 CPU,也不是 GPU。而是:

RenderThread(渲染线程)。

很多大型游戏,包括 Unity、UE、Android、HarmonyOS,都会存在同样的问题。

甚至很多时候:

text 复制代码
CPU 20%
GPU 30%
RenderThread 100%

这才是真正的性能杀手。

一、先理解一帧是怎么出来的

很多人脑海里的流程:

text 复制代码
CPU
 ↓
GPU
 ↓
屏幕

其实真正的流程是:

text 复制代码
Game Thread
      ↓
RenderThread
      ↓
GPU Driver
      ↓
GPU
      ↓
Display

Game Thread

负责:

text 复制代码
AI
物理
动画
状态更新

例如:

ts 复制代码
update() {
    enemySystem.update()
    physicsSystem.update()
}

这是逻辑线程。

RenderThread

负责:

text 复制代码
收集可见对象
排序
Batch
生成 DrawCall
提交 CommandBuffer

例如:

text 复制代码
Player
Enemy
Tree
Bullet
Particle

全部转换成:

text 复制代码
DrawCall

再交给 GPU。

GPU

负责真正绘制:

text 复制代码
Vertex Shader
Fragment Shader
Texture
Rasterization

所以:

text 复制代码
CPU ≠ GPU

中间还有 RenderThread

很多掉帧都发生在这里。

二、为什么 RenderThread 会成为瓶颈?

因为 GPU 不会直接理解:

text 复制代码
Player
Enemy
Bullet
UI

GPU 只能理解:

text 复制代码
CommandBuffer
DrawCall
Vertex Buffer
Texture

因此,游戏世界:

text 复制代码
Player
Enemy
Tree
Particle

必须经过:

text 复制代码
RenderThread

转换成:

text 复制代码
GPU Command

整个过程类似翻译:

text 复制代码
游戏对象
↓
RenderThread
↓
GPU语言
↓
GPU

如果翻译速度跟不上,GPU 就会等待,于是出现:

text 复制代码
GPU 利用率很低

FPS 却很低

真正卡住的是:

text 复制代码
RenderThread

三、RenderThread 每帧都在做什么?

一帧里面,它其实很忙。

1、Visible Culling

判断哪些对象可见:

text 复制代码
10000 个对象
↓
2000 个可见

例如:

ts 复制代码
for (obj in world) {
    if (camera.contains(obj)) {
        visibleList.push(obj)
    }
}

这是 CPU 运算。

2、Render Queue 排序

为了减少状态切换,会按照:

text 复制代码
Material
Texture
Shader

排序:

text 复制代码
A
A
A
B
B
B

而不是:

text 复制代码
A
B
A
B
A

否则,GPU 状态切换非常昂贵。

3、Batch

把:

text 复制代码
1000 个 Sprite

合并成:

text 复制代码
10 个 DrawCall

例如:

text 复制代码
Enemy1
Enemy2
Enemy3
Enemy4
text 复制代码
DrawCall #1

Batch 本身也是 CPU 工作。

4、Command Buffer

生成:

text 复制代码
DrawIndexed()
BindTexture()
BindShader()

然后提交给 Driver,这一部分全部运行在:

text 复制代码
RenderThread

四、为什么 DrawCall 太多会卡 RenderThread?

例如,场景中:

text 复制代码
5000 个对象

每个对象:

text 复制代码
1 DrawCall

那么:

text 复制代码
5000 DrawCall

RenderThread 每帧都要:

text 复制代码
排序

状态切换

生成命令

60FPS 下,每秒:

text 复制代码
30 万次 DrawCall

CPU 时间大量消耗在:

text 复制代码
Driver
API Call

于是:

text 复制代码
RenderThread = 100%

GPU:

text 复制代码
等待数据

利用率只有:

text 复制代码
20%

表现出来就是:

GPU 很闲,游戏却很卡。

五、UI 为什么特别容易卡 RenderThread?

这是 HarmonyOS 游戏最容易踩坑的地方,例如:

ts 复制代码
Column() {

    ForEach(items)

}

里面有:

text 复制代码
1000 个节点

每次:

ts 复制代码
score++

触发:

text 复制代码
build()

导致:

text 复制代码
整个节点树重新布局
重新生成 RenderNode
重新提交

最终,压力全部来到:

text 复制代码
RenderThread

表现:

text 复制代码
CPU正常

GPU正常

FPS下降

实际上:

text 复制代码
RenderThread 爆掉

六、Particle 为什么容易炸?

例如,屏幕上:

text 复制代码
3000 粒子

每个粒子:

text 复制代码
位置
旋转
缩放
透明度

都在变化,意味着每帧:

text 复制代码
VertexBuffer 更新

RenderThread:

text 复制代码
Upload Buffer
生成 Command
提交 GPU

CPU 开销巨大,所以很多游戏,粒子数量超过:

text 复制代码
5000

FPS 会瞬间下降,并不是 GPU 不行。而是:

text 复制代码
RenderThread 来不及提交。

七、真正优秀的游戏都在减少 RenderThread 工作量

核心思想:

让 RenderThread 少干活。

1、Batch

错误:

text 复制代码
1000 DrawCall

优化:

text 复制代码
20 DrawCall

2、Atlas

错误:

text 复制代码
100 张 Texture

优化:

text 复制代码
1 张大图

减少:

text 复制代码
BindTexture()

次数。

3、Instancing

例如:

text 复制代码
1000 棵树

错误:

text 复制代码
1000 DrawCall

优化:

text 复制代码
1 DrawCall

GPU 自己复制,RenderThread 几乎不增加负担。

4、缓存静态对象

例如地图,错误每帧:

text 复制代码
重新生成 Mesh

优化:

text 复制代码
缓存 VertexBuffer

直接复用。

八、为什么现代游戏都在多线程渲染?

因为单 RenderThread 已经不够用了,传统:

text 复制代码
GameThread
    ↓
RenderThread
    ↓
GPU

RenderThread 成为唯一瓶颈,于是现代引擎开始:

text 复制代码
GameThread
      ↓
RenderTask1
RenderTask2
RenderTask3
      ↓
RenderThread
      ↓
GPU

例如:

1、Culling 一个线程。

2、Batch 一个线程。

3、Shadow 一个线程。

4、Particle 一个线程。

最后,RenderThread 负责汇总。结构变成:

text 复制代码
Job System
      ↓
Worker Thread
      ↓
RenderThread
      ↓
GPU

这也是:

text 复制代码
Unity DOTS

UE5

现代手游

越来越快的原因。

九、HarmonyOS 游戏未来为什么一定会走向 Job System?

因为未来越来越复杂:

text 复制代码
Agent NPC

大地图

物理

动画

AI

网络同步

全部放在:

text 复制代码
Main Thread

一定会卡死,未来架构:

text 复制代码
MainThread
        ↓

 Job System

 AI Thread
 Physics Thread
 Animation Thread
 Render Thread

        ↓

GPU

形成:

text 复制代码
多核 CPU
↓
并行计算
↓
稳定 120FPS

这也是未来 HarmonyOS 游戏架构的发展方向。

总结

很多开发者看到掉帧时,第一反应是:

GPU 不够强。

实际上大量游戏真正的瓶颈是:

text 复制代码
RenderThread

因为:

text 复制代码
CPU
↓
RenderThread
↓
GPU

中间这层,负责把游戏世界翻译成 GPU 能理解的语言。

最后一句话总结:

RenderThread 才是 CPU 和 GPU 之间那座最容易堵车的桥。

很多时候,不是 GPU 画不动。而是:

RenderThread 根本来不及把数据送过去。