对 Web 平台支持的可行性

在上一篇文章中,我提到过想要编写一个游戏引擎,但并未具体说明目标是什么。现在,先为这个游戏引擎设定一个初步构想:它必须具备跨平台能力。到了 2025 年,尽管大部分游戏依然以 Windows 为主,但越来越多游戏已同时支持 Linux 和 macOS,而且主流游戏引擎(如 Unreal、Unity)都已实现跨平台支持。

作为一个 Web 出身的开发者,我认为 Web 平台也不容忽视。因此,本文将重点探讨如何让以 C++ 编写的游戏引擎在 Web 平台上运行的可行性。‌

C++ 方面的考虑

WebAssembly 目前已经发布了很多年,许多语言都可以编译成 wasm 的二进制,从而让 Web 运行。那要让 C++ 编译成 wasm 就得使用 Emscripten。目前来说 Emscripten 也支持了绝大多数 C++ 20/23 的特性,不过很可惜的是,不支持 C++ modules。这点真的很可惜,模块功能自从 C++ 20 提出,到 C++ 23 支持 std 模块,已经过去了很多年。Emscripten 是基于 llvm,而最新的 llvm 版本已经支持 std 模块了。从 Github 的相关 issue 也看不到什么时候会提供支持。

emscripten-modules-issue.png

如果要考虑支持 Web 平台,就得放弃 C++ 模块这个特性。

系统 API 的差异

众所周知,浏览器中的 Wasm 虚拟机运行在沙箱环境中,无法直接访问操作系统底层 API,例如文件系统、音频、图形和网络。而这些功能对游戏引擎而言至关重要。下面逐项探讨在 Web 平台上如何应对这些差异。

文件系统

游戏运行时往往需要加载大量资源文件。在原生平台,通常直接调用文件读写 API 即可。然而在 Web 环境下,资源文件一般托管在服务器上,需要通过网络获取。对于普通的 JavaScript 应用,只需使用 fetch 即可完成远程资源加载;但 Wasm 无法直接调用浏览器的 Web API,需要通过 JavaScript 桥接。‌好在 Emscripten 已经完成了这方面的封装。根据官方文档,只需调用 emscripten_fetch 即可从远端下载数据。

例如:

1#include <stdio.h>
2#include <string.h>
3#include <emscripten/fetch.h>
4
5void downloadSucceeded(emscripten_fetch_t *fetch) {
6  printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
7  // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
8  emscripten_fetch_close(fetch); // Free data associated with the fetch.
9}
10
11void downloadFailed(emscripten_fetch_t *fetch) {
12  printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
13  emscripten_fetch_close(fetch); // Also free data on failure.
14}
15
16int main() {
17  emscripten_fetch_attr_t attr;
18  emscripten_fetch_attr_init(&attr);
19  strcpy(attr.requestMethod, "GET");
20  attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
21  attr.onsuccess = downloadSucceeded;
22  attr.onerror = downloadFailed;
23  emscripten_fetch(&attr, "myfile.dat");
24}

只要在引擎中对原生文件 I/O 与 Emscripten 的 fetch 做一层抽象,就能实现统一的资源加载接口。‌

音频系统

在浏览器环境中,播放音频需要使用 <audio> 标签或 Web Audio API。令人惊喜的是,Emscripten 对 OpenAL 1.1 提供了完整支持,并以 Web Audio API 作为其后端实现。‌ 这意味着,只要在引擎中使用 OpenAL,就无需额外关注底层浏览器音频细节,Emscripten 会自动将 OpenAL 调用映射到 Web Audio API,从而保证跨平台一致性。‌

emscripten-openal.png

网络

浏览器无法直接访问底层 TCP/UDP 套接字,只能使用 WebSocket、Fetch 等高层网络 API。而大多数游戏在实时通信时底层往往依赖 UDP,这与浏览器环境存在差异。

在这里,Web 与原生平台的行为差异可能是无法避免的。但对我而言,这不是当前的重点:构建一个复杂到服务端逻辑也要支持的“大型”游戏引擎并不我的目标。

因此,网络层的兼容性问题可以暂时搁置。

图形API

在图形渲染方面,Web 平台的杀手锏是 WebGPU。WebGPU 1.0 规范已稳定,同时在原生平台上也有对应实现——在 Windows 上通过 DirectX 12,在 Linux 上通过 Vulkan,在 macOS 上通过 Metal 等。‌

这正好契合跨平台开发的需求:引擎只需使用 WebGPU 接口编写渲染模块,便可在原生平台调用对应后端,在浏览器端调用 WebGPU。Emscripten 同样支持调用 WebGPU API,让 Wasm 代码能够直接与浏览器的图形管线对接。这样一来,就无需在引擎内部再维护不同平台下的图形后端实现。‌

webgpu.png

结论

Web 平台完全可以作为游戏引擎的一个支持目标。不过,目前我对整个项目还处于探索阶段,短期内会优先在 Windows、Linux、macOS 三大原生平台上进行开发与验证,在未来适当的时机再考虑输出至 Web 平台