网页游戏卡顿?三步重构渲染管线,帧率飙升200%
|
去年十月,我蹲在成都那个漏水的机房里改《剑魄》的渲染循环,天花板滴水声跟 requestAnimationFrame 的节拍完全对不上——第三滴砸在UPS散热口上时,李哲正用指甲刮鼠标滚轮,刮得我太阳穴突突跳。他刮了十八秒,没说话,但我知道他在等我删掉那行「gl.clearColor(0,0,0,0)」。 我原以为是纹理上传阻塞,结果发现根本没人管过 uniform location 缓存。美术导出的 GLB 文件里有七处 name 写成「NormalMap」、「normalmap」、「NORMAL_MAP」、「_normal」、「nrm」、「Bump」、「diffuseNormal」——全是同一张法线贴图,全被 runtime 当新 uniform 重绑定。光这一项,每帧多调八次 getUniformLocation。不是八毫秒,是八次 CPU 到 GPU 的状态同步开销。我说不准具体耗多少帧,但我在 Chrome 的 Performance 面板里拖动 timeline 到第 4.7 秒时,那一帧的 Call Stack 里,uniformLocation 查找占了整整三百七十二毫秒的主线程时间。这个我敢打包票。 王薇当时端着杯冷透的珍珠奶茶进来,吸管插了三次才捅破封膜,奶茶漏了一滴在她左手无名指的银环上。她问:“能砍掉 shader 变体吗?”我说不能,她说:“那把所有顶点属性 layout 全改成 location=0 开始硬编码?”我点头,她就掏出 iPad 打开 Figma,切到「角色渲染层规范」页,把那张画了三年还没定稿的 attribute mapping 表删了,直接写上「a_pos→0,a_uv→1,a_nor→2」——底下三十七个子模型的 glsl 都崩了,但她不补,就叉掉窗口去吃桌角那份没动过的鱼香肉丝盖饭,米粒沾在右耳垂上。 第二步是裁剪逻辑下推。不是改 frustum cull,是把 visibility 状态缓存从 CPU 偷渡进 Transform Feedback Buffer。这个事我没干干净净做完——只干了一半,只把骨骼动画的 visible 标志打进了 TBO,其余静态物件还在 render loop 里逐个 if(visible);后来测出来,这一半也够把 draw call 从二百一十三压到八十九。可我没优化完,因为周三下午三点,运维小哥敲门说楼顶蓄水池爆了,水顺着消防管道流进隔壁测试区,三十台安卓平板泡在浅水里还在跑 Lottie 动画。我就停了,去帮他们捞设备。那批平板后来擦干开机,Lottie 还在播,声音没了,但进度条照走。没人修音频,也没人关掉——这事儿就这么悬着。 第三步最贱:我们砍掉了「渐隐销毁」。以前粒子消失要 run 0.3 秒 alpha fade,现在改成本地计数器满格就直接 pop()。美术组炸了,陈默摔了他那个带温度传感器的咖啡杯——杯子没碎,传感器屏幕闪了下“42℃”,然后黑了。他指着粒子编辑器里三百二十六个已存档预设吼:“你们连‘消散’都要阉?”没人回他。我就盯着他左手小指第二节弯着没伸直,像卡在某个 keyframe 里,一直没动。其实我们留了后门:alpha=0 的粒子还保留在 VBO 里,只是 drawIndirect 参数把它 skip 了。但这话我没说,说了像解释,解释就是认怂。 帧率涨了。不是平滑上升,是断崖式跳变——从 32 崩到 96,中间没有过渡帧。就像把卡住的磁带猛抽出来再塞回去,咔一声,声音回来了,但前半秒节奏全乱。有个 QA 用三星 S21 测,录屏时手抖,镜头晃进窗外树影,光斑在屏幕上跳了四下,正好对应四帧渲染错位。这事儿后来没人提,因为第四天上线灰度,数据看板里首帧耗时曲线像被刀削过:左边是陡坡,右边是平地,中间没过渡。运营在钉钉发了个哭笑不得表情包,后面跟了句:“用户没骂,但留存+11%,是不是卡得太久,突然流畅反而像 bug?”
文章配图,仅供参考 优胜劣汰?呵。不是淘汰差代码,是淘汰不会抢显存带宽的人。上周我见着一个实习生,用 new Image() 加载 atlas 图片,load 完立刻 drawImage 到 canvas,再 readPixel 提取像素做 runtime color adjust——全程不加 debounce,每帧都新建 Image 实例。我看了两分钟没拦,直到他 console 里飘出第 17 个「Image load failed」。我端起自己那杯放凉的茶,茶叶沉在杯底,一动不动。他抬头看我,我指了指他显示器右下角的时间戳:15:28:03。我说:“你刚点运行的时候,后台正在跑 webpack 构建。”他愣住。我没说下去。其实构建早就完了,我只是想看他眼睛眨几次。 现在每天早上八点,我把 index.html 的 标签手动挪到 前面,挪完喝一口茶。茶凉得很快,但杯子底总剩两片叶子浮着。昨天李哲问我为啥不干脆用 module preload。我没答。因为我不确定 preload 是不是又在下一盘更大的棋——毕竟,上个月我们刚把 WebGL2 context 的 antialias 改成 false,只为省下那零点六毫秒的 MSAA resolve 时间。而今天凌晨,美术组发来一封邮件,标题是《关于增加 PBR 材质通道的紧急需求》,附件里带了一张 16K×16K 的 roughness-metallic 合成图。我下载下来双击打开,Photoshop 卡了四秒半,内存条亮起红灯。我没关它。就让它卡着。 下次重构?等他们把「实时 GI 环境光探针」的需求文档 PDF 发来,我看清第一页有没有写「支持 WebGL1 fallback」再说。要是没写,我就把那 PDF 打印出来,用打火机烧了,看灰怎么落——落得匀不匀,比帧率还准。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


VR云弹性架构:千人并发实训零卡顿
Ruby老兵亲授:网页游戏前端开发避坑指南
云端视觉模型卡顿?三步网络优化达毫秒响应
移动App卡顿真相:控制架构设计之祸
轻量化网站架构:网页游戏畅爽体验重构