如何在 Godot 里跑浏览器?
最近我们在做 CRPG 游戏项目 Engram 的时候遇到了一个很典型的痛点:游戏 UI 开发实在是太折磨人了。
用游戏引擎自带的节点系统去堆一个复杂、响应迅速、有动画、有状态管理的界面,效率低得让人想撞墙。作为对比,网页开发那边有几十年的工具链积累——HTML、CSS、JavaScript,还有 React/Vue 这些现代框架,配合 Chrome DevTools 这种神级调试工具,开发体验简直是降维打击。
当时我就在想:如果能在 Godot 里直接用网页技术写 UI 会怎样?
市面上现有的方案我们都试了一圈,结果都不太理想:
- godot_wry:基于系统原生 WebView,轻量是轻量,但只能覆盖在顶层,没法融入 3D 场景,而且不同系统的浏览器内核行为不一致,调试起来能要人命。
- gdcef:基于 CEF 的 C++ 集成,也是个老牌项目了,但它是纯软件渲染,帧率很难看,4K 下能稳定 15 帧都不容易。
我们在 Engram 的 Demo 版本中使用了 godot_wry。我们甚至给上游交了不少补丁。上线后大概有 50% 的启动崩溃都是因为它在旧版 Windows 上找不到 Edge WebView2,然后 Linux 支持也有问题,继续研发下去能完全改好的希望很渺茫。
既然没有好用的,那就自己造一个吧。于是就有了 Godot CEF。
什么是 Godot CEF?
简单来说,这是一个针对 Godot 4.5+ 的高性能 Chromium 集成。为了内存安全和现代化的开发体验,我选择用 Rust(基于 godot-rust)来写。
核心思路其实很简单:弄一个叫 CefTexture 的节点,它负责把 Chromium 渲染出来的画面弄成一个 Godot 的纹理。
这就意味着你可以像对待普通贴图一样对待它:贴在 2D 画布上当 UI,贴在 3D Mesh 上当屏幕,甚至用 Shader 给它做弯曲、故障效果,完全没问题。
而且因为是基于 Chromium,它和你在 Chrome 里看到的网页是一模一样的。你可以启动一个 Vite 开发服务器,一边改 Vue/React 代码,一边在 Godot 里实时看到热重载的效果。GDScript 和 JavaScript 之间还能通过 IPC 互相发消息,延迟控制在一帧以内,这就很舒服了。
听起来很简单,大不了就是多写点 GPU 编程代码,一开始我是这么想的。
多 GPU 的噩梦
原理听起来简单,但实现起来全是坑。其中最让我很痛苦的是多 GPU 系统的问题。
现在的笔记本电脑通常都是「核显 + 独显」的配置。Godot 为了性能,通常会跑在独显上;而 CEF 的子进程如果不加干预,默认会跑在省电的核显上。
问题来了:跨 GPU 的贴图共享是不存在的。贴图句柄只在创建它的那块显卡上有效。如果 Godot 和 CEF 跑在不同的显卡上,你拿到的就是一个无效的句柄,结果就是黑屏,或者随机崩溃。
为了解决这个问题,我不得不搞了一套 GPU 设备锁定(Device Pinning) 的机制。拿到显卡的厂商 ID 和设备 ID 后,再通过命令行参数传给 CEF 子进程,强行按着它的头让它用同一块显卡。这中间还有无数细节,比如 ID 格式转换(Chromium 要十进制,系统给的是十六进制),稍微错一点就是黑屏。
但是还有更坑的,在一些情况下,CEF 甚至枚举不到合适的设备,这和 NVIDIA Optimus 的行为有关,需要在 helper 进程中暴露一个 NvOptimusEnablement 符号才能选到独显。
Vulkan 扩展注入
解决了显卡选择,下一个问题是 Vulkan 的扩展支持。
要在进程间共享 Vulkan 纹理,需要一些特定的扩展,比如 Windows 上的 VK_KHR_external_memory_win32 或者 Linux 上的 VK_KHR_external_memory_fd。
然而,Godot 在创建 Vulkan 设备的时候,只会请求它自己需要的扩展。GDExtension 目前也没有 API 能让我们请求额外的扩展。
引擎不给 API 怎么办?我用了一套运行时函数钩取(Function Hooking)的方案,直接 Hook 了 vkCreateDevice 这个底层函数。在 Godot 调用它创建设备之前,把我们需要的扩展列表强行塞进去。
但这终究不是长久之计。为了以后能优雅地删掉这些 Hack 代码,我给 Godot 官方提了个 PR (#114940),专门加了一个 API,允许 GDExtension 在设备创建阶段请求额外的 Vulkan 扩展。等这个 PR 合并了,我们就能堂堂正正地用官方 API 了。
顺便提一嘴 macOS。本来我也想在 macOS 上用 Hook 的方法,结果发现 Godot 在 macOS 上是把 MoltenVK(Vulkan 转 Metal 的层)静态链接进去的。这意味着 vkCreateDevice 的调用是直接跳转,根本没有符号表可以 Hook。
还好 macOS 至少能跑 metal API。
结语
Godot CEF 这个项目,最初只是为了解决我做 Engram 时的刚需,但现在我觉得它已经足够成熟,可以拿出来给社区用了。
如果你也受够了在游戏引擎里手搓 UI,或者想在游戏里整点网页的狠活,欢迎来试试。项目是 MIT 开源的,二进制文件也预编译好了,拖进 addons 就能用。
- GitHub: dsh0416/godot-cef
- Godot Assets Store: Godot CEF
Comments