回顾 GDC 与那次移动平台闪电战
一个跑着真实代码的真实引擎,胜过任何一份幻灯片。RakuAI 把这样一个引擎带去了 GDC,同一周上线了两个移动平台,证明了新增一个平台是一个已知的形状——而不是一次重写。
GDC 是一个月前的事了。回程的航班上写满了笔记。那些在走廊和侧会里开始的对话,至今仍在收件箱里回响。今天是合适的一个周六,把出发前那段时间发布了什么、差点没能发布成什么,以及从中得到了什么,写下来。
GDC 是这样一个地方:在三天密集的交谈里,你会发现自己的哪些假设站得住脚,哪些其实只是一个被你当成事实来对待的猜测。我带着这两种答案回了家,还带回了一份比预期更长的、需要跟进的人员名单。
发布标签说明了一切
我随身带去的笔记本电脑上跑的版本是 v1.3.8.26.10.09。把版本号提升到这个值的那次提交,只有一行提交信息:「GDC 2026 Eve Release」。那是 3 月 25 日,第二天早上我就上了飞机。
我想写的正是这次 GDC 前夜的提交,因为它是那个差点没能落地的提交。
起飞前四十八小时
在 GDC 前一周,大家一致认定的「演示构建」以四种不同的方式失败了。修复提交是 3 月 7 日的 b70a48dc,汇总在一个标题为「fix(demos): fix black screens, crashes, and expand API test coverage」的 PR 里。这四种方式是:
黑屏。 动画展示、AI 行为测试、CSG 布尔运算测试,以及玩法框架测试,全都渲染成了黑屏。原因是缺少了一次演示所需的、用来注册调试绘制资源的 raku_editor_init() 调用。CMake 的链接目标在一个 sprint 之前被重构过,编辑器库因此从演示二进制文件里被裁掉了,却没人注意到这些演示其实依赖它。修复方式:重新加回链接,恢复初始化调用。
一次空指针崩溃。 SLM 对话演示在启动时因为一个空的 QA 指针崩溃了。对话模型加载器在「配置未找到」这条路径上提前返回,却没有标记这个错误。修复方式:把错误暴露出来,并发布一份能正常工作的配置。
一个立刻断开连接的网络演示。 网络回显演示打开一个套接字,完成握手,然后就关闭了。断连检测在第一个空闲帧就被触发了,而不是等到真正的超时之后才触发。修复方式:改成真正的超时,而不是「第一个空闲帧」。
一个在没有 OpenXR 运行时的情况下以退出码 1 退出的 XR 测试。 在没有运行时存在的情况下以非零码退出,对于一台可能没安装 XR 运行时的演示机器来说是错误的默认行为。修复方式:优雅地以退出码 0 退出,并记录运行时缺失这一事实。
这些修复没有一个是多么巧妙的,但每一个都是那种在开发者自己的机器上看起来没问题、在陌生人的机器上却看起来是坏的的东西。像这样一次出行前的四十八小时,正是这类失败浮出水面的时刻,因为演示终于要在它接下来一周真正要待的那台笔记本电脑上运行了,而且是在那些会在十五秒内就形成看法的人面前运行。
我需要及时搞定的那些打磨性提交
出发前一天,3 月 26 日又有两次提交。
地面平面的高光问题。 世界模型演示里有一个绿色的地面平面和几个漂浮的形状。套用了一个全局高光材质之后,绿色地面在演示灯光下被冲刷成了白色。修复只用了十行代码:给地面平面用哑光材质,高光只留给动态物体。演示瞬间看起来像是刻意的美术方向,而不是一个调试到一半的引擎。
在会议用笔记本上强制启用 NVIDIA 独立显卡。 同时具备 Intel UHD 核显和 NVIDIA 独立显卡的混合式笔记本电脑,默认会用 Intel 芯片,除非有显式的提示告诉它不要这样。这个提示就是两个导出符号:NVIDIA Optimus 对应的 NvOptimusEnablement,以及 AMD 那边等效的 AmdPowerXpressRequestHighPerformance。把它们加进演示的二进制文件后,就强制用上了独立显卡。现在 HUD 也会在标题栏里打印 GPU 名称,这样我一眼就能看出演示实际跑在什么硬件上。结果是:跑在了 Quadro T2000 上,而不是集成的 UHD 上,帧耗时降低了一半还多。
这两个提交都不超过五十行代码,但每一个都是「演示以三十帧运行」和「演示以九十帧运行、而且看起来是刻意为之」之间的差别。
那个变成了更好展示品的浏览器兜底方案
3 月的那一周落地的另一件事,是位于 web/seed-explorer/ 的一个独立浏览器演示。一个单文件 HTML,零依赖,是运行时原生侧使用的 Simplex 噪声地形生成器的 JavaScript 移植版本。它用 putImageData 把一个生成世界的三个视图渲染到一个 512x512 的画布上:生物群系图、高度图、温度图。里面内置了一个 raku://seed/ URL 方案,所以一个种子(seed)是可以分享的。
这原本只是为了在笔记本电脑出问题或者会场 wifi 一团糟时留一手备用方案。结果它变成了我在侧会交谈中实际打开次数最多的东西。别人放在咖啡桌上的笔记本电脑,没法当场安装我们的 SDK,但可以加载一个 URL。对话就从「把二进制文件发给我」变成了「打开这个链接」。这是一种完全不同的对话。这个浏览器演示会一直留在轮换的演示清单里。
那次移动平台闪电战
三月的另一半时间是 iOS 和 Android,两个平台在同一周内上线。
Android。 F1 系列(F1.2 到 F1.15)的十二次提交,从 NativeActivity 和 JNI 桥接脚手架开始,以 Android 性能分析器结束。中间还包括:通过 JNI 接入的触摸输入、用于 AAB 构建的 GitHub Actions 工作流、Firebase Crashlytics 集成,以及激活作为音频后端的 Oboe。Oboe 是 Android 上低延迟音频的正确选择,围绕它的 C API 包装层让同一套音频回调可以跨平台工作。
iOS。 F2 系列(F2.1 到 F2.7)的七次提交,历时两天。从 Xcode 项目和 Swift 应用外壳开始,加上了一套 arm64 交叉编译工具链,接入了 Metal 作为渲染器(F2.2 那次提交的信息写的是「iOS Metal renderer integration, glue layer, CMake toolchain, 51 tests」,而测试数量正是我最引以为豪的部分),通过 UIGestureRecognizer 加上了手势输入,在 AVAudioEngine 之上搭建了空间音频后端,并通过 URLSession 接入了资产包的下载与缓存。
两个平台得到的是同样的形状:一层很薄的原生层,把触摸、音频、GPU 和资产分发暴露给桌面运行时所使用的那同一套 C API。这就是契约。平台特定的代码是实现。从这里开始,新增一个平台是一个已知的形状,而不是一个开放式的架构难题。
空间音频是被低估的胜利
我一直被问到的那部分工作是空间音频。头文件位于 include/raku/raku_spatial_audio.h。实现按平台拆分:HRTF 处理负责空间定位,混音器组负责逐声道控制,效果器(混响、延迟),以及负责房间声学的混响区。在 Android 上,音频路径走 Oboe;在 iOS 上,走 AVAudioEngine。空间音频的提交是 3 月 6 日的 e0aef6c6。
这对一个 AR 引擎之所以重要,是因为音频是「临场感」的一半。带有立体声(或者更糟,单声道)音频的视觉 AR,会在用户转头的那一刻就破坏沉浸感。随着用户移动而实时更新的空间音频,能让虚拟内容感觉真的存在于这个房间里。代价是真实存在的:HRTF 处理不是免费的。但收益也是真实存在的:从没有人在体验过一个好的空间音频演示之后,还想要把它关掉。
GDC 真正推动了什么
三件值得写下来的事。
合作伙伴对话。 走廊里的交谈和侧会,正是现在陆续排上日历的那些。会议上的演讲很精彩,但演讲之间的那些对话,才是这趟出行值回票价的原因。等我在未来某个周六具体写到合作伙伴方向的时候,这些线索就是那篇文章的实质内容。
对这个领域现状更清晰的判断。 有关于世界模型的分论坛,有关于 AI 驱动内容流水线的,有关于研究演示和可交付运行时之间差距的,也有几家不同厂商关于空间 AR 硬件路线图的。我此前对竞争格局的一些假设被证实是对的。有几件我一直在暗自担心的事,结果发现威胁没有我想的那么大。也有几件我原本没怎么担心的事,结果发现值得更多关注。仅凭这次重新校准,这趟飞行就值了。
继续走下去的许可。 GDC 是一个倒逼机制。我随身带去的那个演示并不完美,但它是具体的,是一个跑着真实代码的真实引擎,回答的是一类和幻灯片完全不同的问题。「给我发一封后续邮件」这种回应的数量之多,足以确认这个方向是对的。这就是那个指标。
移动平台上线了,我展示的演示都跑起来了,浏览器兜底方案变成了一项特性。这个周六过得很值。
为你脸上戴的那个设备打造的运行时
一套 C API,处处皆有的空间音频,以及一个为智能眼镜量身设计、而非从 PC 引擎硬塞进来的、具备热感知能力的运行时。看看你的 AI 在真实世界里活在哪里。