Cocos Creator WebGPU 前端 WASM 化实现:基于 Emscripten 的 gfx-wgpu 后端编译与架构解析
2026/9/15 16:19:52 网站建设 项目流程

Cocos Creator WebGPU 前端 WASM 化实现:基于 Emscripten 的 gfx-wgpu 后端编译与架构解析

【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine

本文基于 Cocos Creator 引擎仓库中 gfx-wgpu 后端说明文档,结合该后端的真实 C++ 源码与 TypeScript 集成代码,系统讲解 Cocos 如何通过 Emscripten 将 WebGPU 图形后端编译为 WASM 在网页上运行。读者将掌握该后端的编译环境搭建、完整编译命令、产物部署方式,以及其"C++ 实现 + embind 导出 + JS 加载"的整体架构设计。

一、背景:为什么需要 WebGPU 后端

1.1 WebGPU 与 WebGL 的本质区别

引擎原生文档对 WebGPU 给出了明确的定义性描述:

WebGPU 是一种提案中的 Web API,用于让网页能够使用系统 GPU 执行计算并绘制可在页面内呈现的复杂图像。这一目标与 WebGL 系列 API 类似,但 WebGPU 能够访问 GPU 更高级的特性。WebGL 主要用于绘制图像,虽然也能(付出巨大代价地)被改造用于其他类型的计算,而 WebGPU 对在 GPU 上进行通用计算提供了一流支持。

两者虽然同属浏览器 GPU API,但定位截然不同:

维度WebGLWebGPU
设计目标以绘制图像为主绘制 + GPU 通用计算(Compute Shader)一等公民
底层模型状态机模型(全局状态绑定)现代图形 API 风格(命令缓冲、管线对象、显式资源屏障)
API 风格类似 OpenGL ES 2.0/3.0类似 Vulkan / Metal / Direct3D 12 的统一抽象
资源管理相对简单、隐式同步显式资源布局、屏障(Barrier)与同步
着色语言GLSL ESWGSL(由 glslang 与 twgsl 转换得到)

对于游戏引擎而言,WebGPU 的意义在于:它把桌面级图形 API 的能力(计算着色器、显式管线状态、更精细的内存控制)带到了 Web 平台,这正是 Cocos 将其作为下一代 Web 渲染后端的重要原因。

1.2 项目背景与基线版本

根据文档记录,该实现基于Cocos Creator v3.6.2emsdk 3.1.17。也就是说,该后端的 C++ 代码与构建脚本以这两个版本为验证基线,后续演进时若更换 Emscripten 工具链版本,需关注 ABI 与链接标志的兼容性。

二、设计决策:为什么用 C++ 编译成 WASM,而不是直接写 TypeScript

文档用一个直白的理由回答了这个问题:"同问'为什么要用 C++ 写再编译成 wasm'——通过这种方式,可以复用其他上层调度逻辑的实现,而不必在 TypeScript 中再实现一遍。"

具体来说,这一决策带来了三个可见的架构收益:

  1. 复用 GFX 统一抽象层:Cocos 的渲染器在cocos/renderer下已经拥有一套统一的 GFX(Graphics Foundation eXtension)接口层。gfx-wgpu后端直接实现这套接口(继承cc::gfx命名空间下的基类),从而完整复用上层渲染管线的调度逻辑(场景剔除、渲染队列、管线状态缓存等),无需为 Web 平台单独编写一套 JS 调度代码。
  2. 与其余原生后端共享实现经验gfx-wgpu的实现模式与引擎已有的gfx-webglgfx-webgl2gfx-validatorgfx-agent等后端一致,降低了维护成本。
  3. 性能可控:核心绘制与资源管理逻辑以原生代码运行,仅通过 embind 与 JS 层交换控制数据,关键路径上的数据拷贝(如CCWGPUBuffer::update)针对 WASM 环境做了专门优化。

三、仓库源码布局:gfx-wgpu 后端全景

该后端的全部 C++ 源码位于 native/cocos/renderer/gfx-wgpu 目录:

native/cocos/renderer/gfx-wgpu/ ├── CMakeLists.txt # WASM 构建脚本(Emscripten 链接标志) ├── WGPUDef.h # embind 绑定辅助宏与 JS<->C++ 容器转换 ├── WGPUEMSImpl.cpp # Emscripten 专属桥接实现(val <-> vector 转换) ├── WGPUExports.h # EMSCRIPTEN_BINDINGS 导出清单(结构体 + 类) ├── WGPUDevice.h / .cpp # 设备对象:创建队列、交换链、各类资源 ├── WGPUBuffer.h / .cpp # 缓冲对象 ├── WGPUTexture.h / .cpp # 纹理对象 ├── WGPUSampler.h / .cpp # 采样器对象 ├── WGPUShader.h / .cpp # 着色器对象(WGSL 初始化 + 反射) ├── WGPUCommandBuffer.h / .cpp # 命令缓冲:draw、dispatch、render pass ├── WGPUQueue.h / .cpp # 队列对象:命令提交 ├── WGPURenderPass.h / .cpp # 渲染通道 ├── WGPUFrameBuffer.h / .cpp # 帧缓冲 ├── WGPUInputAssembler.h / .cpp # 输入装配器 ├── WGPUPipelineState.h / .cpp # 管线状态 ├── WGPUPipelineLayout.h / .cpp # 管线布局 ├── WGPUDescriptorSet.h / .cpp # 描述符集 ├── WGPUDescriptorSetLayout.h / .cpp ├── WGPUSwapchain.h / .cpp # 交换链 ├── WGPUQueryPool.h / .cpp # 查询池 ├── WGPUObject.h # 对象基类与哈希工具 ├── WGPUUtils.h / .cpp # 工具函数 └── states/ # 三类显式屏障实现 ├── WGPUBufferBarrier.h / .cpp # 缓冲屏障 ├── WGPUTextureBarrier.h / .cpp # 纹理屏障 └── WGPUGeneralBarrier.h / .cpp # 通用屏障

从这份清单可以看出,该后端覆盖了 GFX 接口要求的全部核心对象:设备、交换链、缓冲、纹理、采样器、着色器、渲染通道、帧缓冲、管线与描述符等,并且在states/子目录中实现了 WebGPU 风格的三类显式资源屏障(通用屏障、缓冲屏障、纹理屏障),这是 WebGPU 相比 WebGL 在同步模型上的关键差异。

四、编译环境搭建

4.1 工具链前置条件

文档明确说明:由于 Emscripten 编译工具链依赖 Unix 的make,因此在Windows 平台上需要额外部署 Unix 环境。需要安装以下 GNUWin32 工具包:

  • make:驱动整个构建过程的构建工具;
  • coreutils:提供rmcp等 Unix 核心命令,供 Emscripten 与 CMake 生成的 Makefile 使用;
  • libiconv:字符集转换库,部分依赖链的编译所需;
  • libintl:国际化消息处理库,配套libiconv使用。

macOS / Linux 环境本身自带上述工具,无需额外安装。Emscripten 工具链的完整安装步骤可参考 Emscripten 官方文档(emcc --versionemsdk install/activate相关流程)。

4.2 验证工具链:emcc -v

文档给出了部署完成后验证工具链的命令与参考输出:

emcc -v

在 Windows 环境下典型的输出为:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.17 (fbc532773d84d2bd7da876275671970e792ad1cd) clang version 15.0.0 (https://github.com/llvm/llvm-project 17e4c217b66305e60657a48f10fe3c428c2fe4d2) Target: wasm32-unknown-emscripten Thread model: posix InstalledDir: D:\project\emsdk\upstream\bin

文档特别提示:在 mac 上该输出会略有不同(主要体现在InstalledDir路径与 clang 版本细节上),这是正常现象。验证要点是确认:编译器前缀为wasm32-unknown-emscripten,版本号与emsdk 3.1.17对应,clang 为 15.0.0 系列。

五、编译流程:从源码到 WASM 产物

5.1 标准编译命令

进入 gfx-wgpu 源码目录后,执行两步编译:

cd engine-native/cocos/renderer/gfx-wgpu emcmake cmake . emmake make
  • emcmake cmake .:用 Emscripten 的 CMake 工具链包装器生成 Makefile;
  • emmake make:调用 Emscripten 包装后的 make 完成编译与链接,最终生成.js.wasm文件。

5.2 自定义改动与手动编译

文档明确说明:对 WGPU C++ 文件的修改,只在手动执行上述编译后才生效。也就是说,当前仓库内已编译好的产物(位于engine/native/external/emscripten/webgpuengine/cocos/webgpu)是预先构建的二进制,修改任何gfx-wgpu下的.cpp/.h后,必须重新手动编译并替换产物。

编译完成后,需要把生成的两个文件复制到引擎的两个目标位置:

  1. 生成的.js.wasm复制到engine/native/external/emscripten/webgpu,替换原有文件;
  2. 复制到engine/cocos/webgpu,替换原有文件。

5.3 编译输出目录的配置佐证

产物的输出位置在 CMakeLists.txt 中直接指定:

set_target_properties(${APP_NAME}_wasm PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ENGINE_ROOT_DIR}/external/emscripten/webgpu)

其中ENGINE_ROOT_DIRCMAKE_CURRENT_LIST_DIR/../../..计算得到,即从native/cocos/renderer/gfx-wgpu上溯三级到达引擎仓库根目录,最终产物落在native/external/emscripten/webgpu。这与文档描述的部署目标完全一致,只是文档中的engine\native\external\emscripten\webgpu即仓库中的native/external/emscripten/webgpu

5.4 Emscripten 链接标志详解

CMakeLists.txt 中定义了完整的链接标志,这些标志直接决定了 WASM 产物如何生成、导出与运行:

set(EMS_LINK_FLAGS "-flto --bind --no-entry -O3 -s USE_ES6_IMPORT_META=0 -s EXPORT_ES6=1 -s MODULARIZE=1 -s EXPORT_NAME='wasmDevice' -s ENVIRONMENT=web -s WASM=1 -s USE_WEBGPU=1 -s NO_EXIT_RUNTIME=1 -s LLD_REPORT_UNDEFINED -s ALLOW_MEMORY_GROWTH=1" )

各标志含义与作用如下:

标志作用
-flto启用链接时优化(Link-Time Optimization),跨编译单元内联优化
--bind启用 embind,将 C++ 类/结构体绑定导出到 JS
--no-entry不生成_main入口函数,产物作为可加载库使用
-O3最高优化级别
-s USE_ES6_IMPORT_META=0关闭 ES6import.meta依赖
-s EXPORT_ES6=1以 ES6 模块形式导出
-s MODULARIZE=1模块化包装,通过工厂函数实例化
-s EXPORT_NAME='wasmDevice'导出工厂函数名为wasmDevice(与 JS 侧加载代码对应)
-s ENVIRONMENT=web产物目标环境限定为浏览器 Web 环境
-s WASM=1生成 WASM 二进制
-s USE_WEBGPU=1启用 Emscripten 的 WebGPU 支持(对应浏览器navigator.gpu
-s NO_EXIT_RUNTIME=1运行时不退出,保持全局状态常驻
-s LLD_REPORT_UNDEFINED链接时报告未定义符号
-s ALLOW_MEMORY_GROWTH=1允许 WASM 线性内存动态增长

Debug 构建还会追加调试标志(CMakeLists.txt):

if(CMAKE_BUILD_TYPE STREQUAL "Debug") string(APPEND EMS_LINK_FLAGS " -g -s ASSERTIONS=2") endif()

其中-g保留调试信息,-s ASSERTIONS=2开启 Emscripten 运行时断言检查。工程同时固定了 C++17 标准、禁用异常(-fno-exceptions),Release 构建使用-O3 -DNDEBUG=1(见 CMakeLists.txt)。

5.5 构建依赖范围

从 CMakeLists.txt 可以看出,WASM 产物并不是只编译 gfx-wgpu 自身,而是打包了以下渲染栈依赖:

  • cocos/basecocos/base/threading(排除ZipUtils.mm文件)
  • cocos/renderer/gfx-base(排除SPIRVUtils.cpp)及gfx-base/states
  • cocos/renderer/gfx-agent(代理层)
  • cocos/renderer/gfx-validator(校验层)
  • cocos/renderer/gfx-empty(空后端)

最终生成名为${APP_NAME}_wasm(即webgpu_wasm)的可执行目标,产物模块化导出名与 JS 侧加载逻辑严格对应。

六、运行时集成:JS 侧如何加载 WASM 后端

WASM 产物最终由引擎的 TypeScript 层加载。入口位于 cocos/webgpu/instantiated.ts,该文件完整展示了运行时的加载与初始化流程:

6.1 模块资源导入

import { NATIVE_CODE_BUNDLE_MODE, WEBGPU } from 'internal:constants'; import webgpuUrl from 'external:emscripten/webgpu/webgpu_wasm.wasm'; import glslangUrl from 'external:emscripten/webgpu/glslang.wasm'; import twgslUrl from 'external:emscripten/webgpu/twgsl.wasm'; import wasmDevice from 'external:emscripten/webgpu/webgpu_wasm.js'; import glslangLoader from 'external:emscripten/webgpu/glslang.js'; import twgslLoader from 'external:emscripten/webgpu/twgsl.js';

共加载三组资源,对应三条职责链:

  • webgpu_wasm:gfx-wgpu 后端主体(webgpu_wasm.js+webgpu_wasm.wasm),即本文第五章编译的产物;
  • glslang:GLSL 转 SPIR-V 的编译库(glslang.js+glslang.wasm);
  • twgsl:WGSL 转换/校验库(twgsl.js+twgsl.wasm)。

这与 WebGPU 着色器管线的现实需求一致:引擎上层仍以 GLSL 编写着色器,运行阶段需要 glslang 编译到 SPIR-V,再由 twgsl 转换到 WGSL 提交给浏览器 WebGPU API。

6.2 初始化流程

export const promiseForWebGPUInstantiation = (() => { if (WEBGPU && NATIVE_CODE_BUNDLE_MODE !== NativeCodeBundleMode.ASMJS) { return Promise.all([ glslangLoader(...).then((res) => { glslangWasmModule.glslang = res; }), twgslLoader(...).then((data) => { twgslModule.twgsl = data; }), fetch(webgpuUrl).then(... wasmDevice(gfx).then(() => { legacyCC.WebGPUDevice = gfx.CCWGPUDevice; resolve(); })), navigator.gpu.requestAdapter().then((adapter) => { adapter.requestDevice().then((device) => { webgpuAdapter.adapter = adapter; webgpuAdapter.device = device; }); }), ]).then(() => Promise.resolve()); } return Promise.resolve(); })();

初始化分为四路并行任务:

  1. 加载 glslang 模块并存入glslangWasmModule.glslang
  2. 加载 twgsl 模块并存入twgslModule.twgsl
  3. fetchwebgpu_wasm.wasm二进制,调用wasmDevice(gfx)工厂函数实例化 WASM 模块,并将gfx.CCWGPUDevice挂载为legacyCC.WebGPUDevice——这正是 CMakeLists.txt 中EXPORT_NAME='wasmDevice'标志的对应消费方;
  4. 通过浏览器标准navigator.gpu.requestAdapter()/requestDevice()获取底层 WebGPU 设备,存入webgpuAdapter

初始化完成后,通过legacyCC.game.onPreInfrastructureInitDelegate.add(() => promiseForWebGPUInstantiation)将整个 Promise 挂接到引擎游戏基础设施初始化之前的生命周期钩子中,保证在渲染器启动前 WebGPU 设备就绪。

值得注意的是入口处的条件判断:仅在WEBGPU特性开关打开且代码打包模式不是 ASMJS 时才执行上述加载(instantiated.ts中的 TODO 注释也表明 AsmJS 回退方案尚未实现)。

七、C++ 实现架构:embind 绑定与桥接细节

7.1 绑定导出层

C++ 侧通过 WGPUExports.h 中的EMSCRIPTEN_BINDINGS(WEBGPU_DEVICE_WASM_EXPORT)块将整个 GFX 对象体系暴露给 JS。导出内容分为两大类:

(1)值对象(value_object):全部核心 GFX 结构体,包括:

  • 资源描述:BufferInfoTextureInfoTextureViewInfoSamplerInfoTextureSubresLayersTextureCopyTextureBlitBufferTextureCopy
  • 渲染描述:ColorAttachmentDepthStencilAttachmentSubpassInfoSubpassDependencyRenderPassInfoFramebufferInfo
  • 管线描述:InputStateRasterizerStateDepthStencilStateBlendState/BlendTargetPipelineStateInfoPipelineLayoutInfo
  • 着色器描述:ShaderStageUniformUniformBlockUniformSamplerTextureUniformStorageImageUniformStorageBufferAttributeShaderInfo
  • 屏障描述:GeneralBarrierInfoTextureBarrierInfoBufferBarrierInfoSubpassDependency
  • 其他:DeviceCapsDeviceInfoDrawInfoDispatchInfoIndirectBufferViewportRectColorSizeExtentQueueInfoQueryPoolInfoDynamicStatesMemoryStatus

(2)类绑定(class_)Device/CCWGPUDeviceSwapchain/CCWGPUSwapchainRenderPassTextureFramebufferSamplerBufferDescriptorSetLayoutDescriptorSetPipelineLayoutShaderInputAssemblerCommandBufferQueuePipelineState、三类 Barrier 等。每个类都通过.property(...)/.function(...)暴露成员与对象 ID,例如CCWGPUDevice暴露了createBuffercreateTextureacquirepresenthasFeaturegetFormatFeaturescopyBuffersToTexture等核心能力。

7.2 结构体导出的宏机制

WGPUDef.h 通过两个核心宏完成结构体导出:

#define EXPORT_STRUCT_POD(struct_name, ...) // 全部字段 POD 的结构体 #define EXPORT_STRUCT_NPOD(struct_name, ...) // 含指针字段的结构体(如 buffer、texture、sampler 指针)

并配合REGISTER_GFX_PTRS_FOR_STRUCTcc::gfx命名空间下所有导出类型特化emscripten::internal::TypeID<T*>,从而允许在 JS 与 C++ 之间安全传递对象指针。枚举类型通过GetType模板自动以底层整型(std::underlying_type)在 JS 侧表示(见 WGPUDef.h)。

7.3 JS 数组与 C++ 容器的桥接

WebGPU 的着色器、缓冲上传等场景需要频繁在 JS 数组与 C++std::vector之间传数据。WGPUEMSImpl.cpp 提供了两条转换路径:

  • convertJSArrayToNumberVector_local<T>:通过typed_memory_view将 JS TypedArray 直接set进 C++ 内存视图,文档注释注明这是"当前从 JS 数组到 vector 最快的方式";
  • vecFromJSArray_local<T>:逐元素as<T>()拷贝的通用路径。

在此基础上,缓冲上传与命令缓冲更新都走了快速路径,例如CCWGPUBuffer::update

void CCWGPUBuffer::update(const val& v, uint32_t size) { ccstd::vector<uint8_t> buffer = convertJSArrayToNumberVector_local<uint8_t>(v); update(reinterpret_cast<const void*>(buffer.data()), size); }

即将 JS 侧传入的 TypedArray 零拷贝式(一次内存视图写入)转为 C++ 缓冲并进入 GFX 上传流程。

7.4 状态屏障(Barrier)体系

states/目录下实现的三类屏障对象是 WebGPU 后端区别于传统 WebGL 后端的核心特征:WebGPU 要求对缓冲、纹理等资源的跨队列/跨阶段访问显式声明屏障。这三类屏障(WGPUGeneralBarrierWGPUBufferBarrierWGPUTextureBarrier)在 WGPUExports.h 中均以constructor<BarrierInfo>()形式绑定导出,其prevAccesses/nextAccesses等字段描述了访问切换前后的资源访问状态。CCWGPUDevice还提供了enableAutoBarrier接口(见 WGPUExports.h),用于控制是否由引擎自动插入屏障。

7.5 条件编译与多后端宏

WGPUDef.h 顶部定义了三个条件编译宏:

#ifdef CC_WGPU_WASM #define EXPORT_EMS(expr) expr #ifdef CC_WGPU_DAWN #define EXPORT_DAWN(expr) expr #ifdef CC_WGPU_RS #define EXPORT_RS(expr) expr

从源码结构看,该目录的设计允许同一套WGPU*实现被不同 WebGPU 绑定路径复用(WASM/Emscripten、Dawn 原生、Rust 绑定),当前仓库主要落地的是CC_WGPU_WASM路径。

八、实践指引:修改后如何重新集成

综合文档与源码,对 gfx-wgpu 后端做一次完整改动闭环的操作流程如下:

  1. 修改源码:编辑 native/cocos/renderer/gfx-wgpu 下的.cpp/.h文件;
  2. 重新编译:在native/cocos/renderer/gfx-wgpu目录下执行emcmake cmake .emmake make
  3. 产物替换:将新生成的.js.wasm复制到native/external/emscripten/webgpu以及cocos/webgpu,替换旧产物;
  4. 验证加载:以WEBGPU特性开启的方式构建网页端包,确认 cocos/webgpu/instantiated.ts 中wasmDevice(gfx)工厂能正常实例化,legacyCC.WebGPUDevice成功挂载;
  5. 注意约束:产物仅面向 Web 环境(-s ENVIRONMENT=web),无_main入口,必须以模块化方式加载;WebGPU 特性依赖浏览器对navigator.gpu的支持情况。

九、已知边界与后续方向

原文档在"Implementation / C++ part"一节以TODO;结尾,说明该文档本身是工程演进中的阶段性说明,未完整铺开每个类内部的实现细节。结合当前仓库源码可以确认的边界包括:

  • 着色器路径CCWGPUShader通过initWithWGSL初始化、reflectBinding反射绑定(见 WGPUEMSImpl.cpp),着色器最终以 WGSL 形态交给浏览器;
  • AsmJS 回退未实现:cocos/webgpu/instantiated.ts 中明确标注 TODO;
  • 拷贝路径简化copyTextureToBuffers当前为占位实现(见 WGPUEMSImpl.cpp);
  • 纹理屏障/刷新命令接口:部分接口在 WGPUExports.h 中以注释形式挂起,属于后续待补全项。

这些 TODO 恰是该后端未来迭代的可见方向:完善纹理回读、补充自动屏障策略、支持更多 WebGPU 特性集。


延伸阅读:感兴趣的读者可以继续深入阅读 native/cocos/renderer/gfx-wgpu/CMakeLists.txt 了解完整构建依赖,通过 native/cocos/renderer/gfx-wgpu/WGPUExports.h 掌握全部导出接口,并结合 cocos/webgpu/instantiated.ts 追踪运行时初始化链路;引擎 WebAssembly 支持框架可参见 cocos/misc/webassembly-support.ts。

【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询