上一篇的结尾我们留了一个问题:一款 AAA 游戏要进入移动设备,到底还差哪些。常见的回答是「性能优化」,但这个词太笼统,笼统到没法安排工作。把它拆开,差距其实是三张表:一张预算表,规定每一帧可以花掉多少资源;一张调度表,规定比内存大得多的世界如何流进流出;一张节奏表,规定这些帧以怎样的间隔到达玩家的眼睛。

在 AAA 工作室里,这三张表由专门的团队维护:引擎组定预算,流送和内存有专人盯,性能组管回归,技术美术把规格翻译给内容团队。所谓「AAA 品质」,一大半是这套隐形组织的产出——而它恰恰是小团队最雇不起的部分。渲染管线、虚拟化几何、开放世界流送这些机制,今天都能从引擎里买到;买不到的是把三张表执行下去的纪律。这篇文章先把三张表讲清楚,再回答上一篇的问题:知道了目标平台,美术资源规格和代码结构该怎么组织——以及,为什么这件事正在从「高薪工程师团队」变成「基础设施」。

表一 · 每帧资源预算:多币种记账

帧预算最表层的币种是时间:60 fps 的一帧是 16.6 ms,120 Hz 是 8.3 ms,30 fps 是 33.3 ms。但时间只是结算单位,真正被花掉的是另外四种货币:CPU 毫秒、GPU 毫秒、带宽和功耗——移动平台上,后两种才是硬约束。

约束桌面工作站旗舰手机
显存带宽独占,量级接近 1 TB/s几十 GB/s,且与 CPU 共享
功耗上限单显卡数百瓦整机个位数瓦特,还要过热墙
内存物理内存基本可用系统在远低于物理内存的水位杀进程
持续性能≈ 峰值性能峰值只能维持几分钟,预算须按持续频率定

这张对比表解释了移动 GPU 为什么普遍是 tile-based 架构:把一小块画面的中间结果留在片上内存里算完再写回,因为去主存走一趟的代价既是带宽也是电。它同时规定了移植的第一课——同一套资产直接下放,等于把桌面的带宽账单原样寄给手机:贴图每多一次采样、渲染目标每多一次 load/store、每一次中途 resolve,都在同时消耗两种最稀缺的货币。热约束则改写了预算的分母:跑分频率下的 16.6 ms 不算数,能连续玩四十分钟不降频的频率下的 16.6 ms 才算数。

预算要可执行,就必须拆成科目。「这一帧 16.6 ms」是愿望;「阴影 1.5、不透明主 pass 4.5、光照 2.5、透明与特效 2.0、后处理 2.0、UI 0.8、流送与杂项 1.0、余量 2.3」才是合同——超支时能立刻定位到科目,而不是「游戏变卡了」。CPU 侧同理:逻辑线程、渲染提交、worker 池各记各的账。

GPU 帧预算账本(60 fps 中端机 · 示例口径) 0 8.3 16.6 ms 阴影 1.5 不透明主 pass 4.5 光照 2.5 透明·特效 2.0 后处理 2.0 UI 0.8 流送 1.0 余量 2.3 超支的代价不是线性的 vsync 0 16.6 33.3 ms 这一帧画了 17.1 ms 空转,等下一个 vsync 槽 超支 0.5 ms → 观感 30 fps
图 1 · 帧预算账本与 vsync 量化。上:预算拆成科目才可执行,余量本身也是科目。下:垂直同步把时间切成 16.6 ms 的槽位,超支 0.5 ms 的帧要占两个槽——所以「60 fps」的真实含义是「每一帧都在 16.6 ms 以内」,预算必须自带余量。

预算表的存在方式决定它是否有效。写在 wiki 里的预算只能靠 review 时想起来;有效的预算长在流水线里——资产导入时校验规格,抓帧数据和账本逐科目对账,超支挡在合入之前。这也是本文结尾要回到的主题。

表二 · World Streaming:跟着相机走的调度器

开放世界的前提是世界比内存大,于是「哪些资源此刻在内存里」成了一个每帧都要重新回答的问题。答案跟着相机走:世界被切成 cell,远处用 HLOD 顶替,相机周围维持一个驻留集。听起来朴素,但工程上它是一个真正的调度器,有自己的优先级函数和每帧预算。

优先级不只看距离。速度要参与预测——预取环应当沿移动向量前移,玩家高速驶向的方向比身后更值得占用 IO;可见性要参与排序——墙后的 cell 可以晚到;玩法要能插队——传送点、过场动画的目的地,在触发前就该开始预热。调度器手里的货币则和帧预算直接挂钩:在飞的 IO 请求数、每帧可用的解压毫秒、每帧允许的上传量、驻留池的水位。大文件的上传要切开摊到多帧,逐出要带滞回,否则池子在水位线附近会陷入「逐出—重载」的振荡。

驻留集跟着相机走 速度 驻留 预取(沿速度前移) 可逐出 优先级 = 距离·速度 可见性·玩法 每帧的流送预算 IO 队列 在飞 ≤ 64 解压 ≤ 2 ms/帧 上传 ≤ 16 MB/帧 驻留池 高 / 低水位 逐出(滞回防抖) 释放 大文件切开摊到多帧上传, 避免单帧独占预算造成 hitch
图 2 · 流送调度的两半。左:驻留集是相机的函数,预取环沿速度向量前移,而不是同心圆。右:IO、解压、上传各自领每帧预算,驻留池按高低水位滞回逐出——四个失败模式(hitch、pop-in、抖动逐出、被系统杀)分别对应这条管线上四个失守的位置。

移动平台给这个调度器加了三条特殊规则。第一,池更小,预测的质量直接决定体验——桌面可以靠冗余驻留掩盖预测失误,手机不行。第二,解压不是免费的:主机把解压做进了硬件,PC 有 NVMe 加 DirectStorage 兜底,而手机上每一次解码都在烧那几瓦的功耗预算,压缩率和解码成本必须按平台重新权衡。第三,流送的对象不只是资产——新材质第一次进画面时的管线编译同样会卡帧,PSO 的预热要和贴图、网格一起排进调度表。

调度表的验收方式和预算表一样,不能靠「感觉流畅」。固定的自动飞行路线、长时段的 soak 测试,在真机上跑出 hitch 次数、pop-in 时长、池水位曲线,这些数字才是调度表的成绩单。

表三 · Frame Pacing:平均帧率会说谎

前两张表管的是每一帧花多少;第三张表管的是帧与帧之间的间隔——玩家的体验大半由它决定,而它恰恰是平均帧率完全测不出来的东西。

vsync,每格 16.6 ms → A · 平均 60 fps,节奏稳 B · 平均 60 fps,节奏烂 40 ms hitch · 三个槽显示同一帧(p99 在这里) 两行帧数相同、平均帧率相同;玩家的眼睛读的是间隔的分布,不是均值。
图 3 · 同样的平均帧率,不同的游戏。A 行每帧都按时落进 vsync 槽;B 行在 8–40 ms 之间抖动,快的帧被垂直同步压平,慢的帧让连续几个槽重复同一画面。有效的指标是帧时间的 p95 / p99、1% low 和每分钟 hitch 数(常用口径:超过目标两倍的帧记一次),而不是平均 fps。

节奏失守有两种形态。一种是 hitch——孤立的长帧,肇因排行榜常年稳定:新 shader 变体首次进画面触发的管线编译、落在关键路径上的同步 IO、内存分配或 GC 的尖峰。另一种是 judder——帧时间没有超支,但内容步长与显示节拍失配,画面以不均匀的间隔前进,高帧率下依然肉眼可见。对付前者靠把慢事挪出关键路径(PSO 预热、异步 IO、预算内的流送);对付后者的经典答案是固定步长模拟加渲染插值,让模拟节拍与显示节拍解耦。

平台层还有一组必须逐个平台做对的机制:交换链深度在延迟与吞吐之间的取舍;Android 上要用 Frame Pacing 库对齐 Choreographer 的节拍;iOS 的 ProMotion 不会自动给你 120 Hz,刷新率档位要显式申请;可变刷新率屏幕改变了「必须整除 vsync」的规则,但有下限,低于下限依然回到复制帧。这些细节没有一条是难的,难的是十几条同时做对、并且在每一代新机型上保持做对。

还有一条最容易在测试里漏掉:热降频。前二十分钟节奏完美的游戏,第二十五分钟随着 SoC 降频整体崩塌——三十秒的抓帧看不到它,只有长时段的真机 soak 能看到。节奏表因此是一张 SLO 表:它约束的是帧时间分布在整个游玩时段里的形状,均值在这张表上连一个科目都算不上。

三张表怎么长进工程里

回到上一篇留的两个问题。知道了目标设备与运行平台,美术资源规格和代码结构路径的组织方式,本质上就是让三张表从文档变成流水线。

**美术资源规格,组织成数据而不是文档。**每个设备档位一份机读的规格:贴图的压缩格式与尺寸上限、mip 与通道打包约定、LOD 链的层数与减面比、材质复杂度上限、shader 变体矩阵。DCC 导出即校验,不合规的资产在导入这一刻被拦下,而不是三个月后在真机抓帧里被发现;cook 按平台从同一份源资产产出各档位变体。变体也不必都由人产出——以减面为例,规格里写清每一级 LOD 的目标面数比例与特征保持要求(轮廓、法线、UV、蒙皮权重),自动减面器就能把整条 LOD 链按档位批量生成;我们的工具链里已经有这样一个属性感知的 QEM 边折叠减面组件,保边界、保蒙皮,按目标比例出结果。至于「设备档位」怎么划——不该来自一张手工维护的机型表,而该来自真机上抓回来的帧时间与带宽数据:档位是测出来的,不是猜出来的。

**代码结构路径,围绕一条抽象缝组织。**平台无关的游戏与引擎核心放在一侧,每个平台的实现放进独立的模块与目录,中间是一条清晰的图形与系统抽象层;画质档位用数据驱动的 scalability 配置表达,而不是散落在代码里的条件编译——#ifdef 森林的问题不在丑,在于它让「一套代码」悄悄变成「N 套代码的叠加态」,三张表对不上账。这样组织的直接代价是构建矩阵:平台 × 档位 × 配置的组合爆炸。这正是分布式构建要吃掉的成本——上一篇讲过的 IrisBuild 集群,UE 编辑器 Shader 编译的本地回退从 518 次压到 1 次、Unity IL2CPP 阶段从 236 秒压到 129 秒,就是在为这个矩阵付账。

**预算即门禁,三张表在循环里被执行。**规格校验挡住不合规的资产,构建矩阵产出各平台各档位的包,真机矩阵在夜里跑固定的飞行路线和长时段 soak,抓帧数据和三张表逐科目对账;超支不是一封邮件,而是一次归因——落到具体的提交或具体的资产上,带着证据回到工作区。上一篇文章讲的那个自动化 Loop,在这里完成闭合。

定位到具体提交 / 具体资产,带证据回到工作区 规格机读数据 导入校验DCC 出口 按档位 cook平台变体 分布式构建平台×档位×配置 真机自动化飞行 + soak 抓帧对账三张表在此执行 AI 归因证据可反查 每晚运行。预算超支不是一封邮件,而是一次带证据的归因。
图 4 · 规格到门禁的闭环。三张表长进流水线的样子:规格在导入时执行,预算在抓帧对账时执行,归因把结果送回具体的提交与资产。这条链上每个节点都不需要人值守,人出现在两端——定义规格,以及审阅归因。

创意平权

把三张表摊开之后,可以诚实地回答「小团队能不能做全平台高品质」了。

能被基础设施抹平的,是纪律的成本。预算的对账、流送的验收、节奏的长时段回归、跨平台构建矩阵、真机矩阵的夜间值守——这些工作在 AAA 工作室里对应着一整层高薪的工程组织,而它们恰恰是最可自动化的部分:机制租自引擎,纪律订阅自基础设施。Ycode 把真机、抓帧、自动化回放和带证据的 AI 归因放进同一个工作台,IrisBuild 让构建矩阵的成本随集群摊薄,就是在把这一层组织变成一件可以按容量订阅的东西。

不能被抹平的,是内容与判断。世界观、关卡、手感、审美——内容体量仍然是人的时间,品味仍然是人的决定。但这正是要点:一个三五人的团队,过去要在「把预算表管起来」和「把游戏做好」之间做残酷的取舍,现在可以把全部的人的时间花在后者上。工程师并没有被替代——是他们的纪律被写进了基础设施,从此不必在每个团队里重新雇一遍。

这就是我们说的创意平权:未来更多人能参与高品质互动作品的制作,作品的上限由想象力和审美决定,而不是由雇得起多少高薪工程师决定。三张表还在,只是它们不再挑选谁有资格入场。

这条路的尽头不止于软件。AI infra 正在把「思考」变得越来越快,瓶颈随之移向另一侧:agent 终究要执行 action,而循环里最重的 action——构建、烘焙、减面、帧分析——最终都值得专用的算力,就像编译走向了分布式集群。未来我们还会为游戏开发与迭代定制 ASIC 电路,为生产实时交互世界的流水线提供底层助力。愿景始终是同一个:让玩家在不同创意编织的世界里体验另一种人生,让更多创意以高质量的交互形式展现给大家。

把完整游戏开发闭环,交给一套 AI 驱动的基础设施。

产品页 mixstudio.tech/product/ycode · 社区 Discord · 商务 [email protected]