# 从零开始在Electron集成C++图形引擎opengl

1,891 阅读8分钟

从零开始在Electron集成C++图形引擎opengl

1. 论证需求

将图形引擎vulkan集成到electron中,找到一条合适的技术路线。尝试写一个demo,用vulkan在electron窗体中显示一个自动旋转的立方体。

vulkan没有找到集成到node中技术路线,后期更换图形引擎为OpenGL

对于 Electron 应用来说,渲染进程中的内容主要通过 HTML、CSS 和 JavaScript 来展示和操作,而 C++ 中绘制的图形无法直接显示到 Electron 的渲染进程中。因此,我的作法是将 C++ 绘制的图形渲染到一个 Canvas 元素上,然后在 Electron 的渲染进程中显示这个 Canvas。

最终采用技术方案为:WASM + OpenGL + C++ + Electron

2. C++编译技术方案—WebAssembly(最终方案)

如何将 OpenGL 编译为可在HTML Canvas 中显示的 WebAssembly ,这里提供一个简要的步骤概述:

  1. 将 OpenGL 代码编译为 WebAssembly:

    • 使用 Emscripten 工具链将 C++ 代码编译为 WebAssembly。Emscripten 是一个工具链,可以将 C/C++ 代码编译为 WebAssembly 格式。
    • 安装 Emscripten 并使用 emcc 命令来编译你的 OpenGL C++ 代码。
  2. Electron加载 HTML 页面:

    • 在 HTML 页面中创建一个 Canvas 元素,用于渲染 WebGL 或者 WebAssembly 渲染的图形。
    • 在页面中添加 JavaScript 代码,用于加载和运行编译后的 WebAssembly 模块,并在 Canvas 中渲染图形。
  3. 加载和运行 WebAssembly 模块:

    • 在 JavaScript 中使用 WebAssembly 的相关 API 来加载和实例化编译后的 WebAssembly 模块。
    • 编写 JavaScript 代码来与 WebAssembly 模块进行交互,比如传递数据和调用函数。
  4. 渲染图形到 Canvas:

    • 在 WebAssembly 模块中编写 OpenGL 相关的渲染代码,包括顶点和片段着色器,以及渲染循环等。
    • 在 JavaScript 中编写逻辑来将 WebAssembly 模块渲染的图形绘制到 Canvas 上。

Probably the eventual workflow when developing with WebAssembly:

1_WWDldE9AliRTkXPv3Q5GpA.webp

2.1 工具准备

OS:windows 10 IDE工具: VS Code

2.2 Emscripten环境准备

首先,配置所需要的开发环境。

所需条件:

  • Git — Linux 和 macOS 的机器一般已经预装了,在 Windows 下你可以从这里下载 Git for Windows installer
  • CMake(version 3.29.2) — 在 Linux 或者 macOS 上,使用类似 apt-get 或 brew 这样的包管理器来安装它,请确保依赖以及路径是否正确。在 Windows 上,使用 CMake installer
  • Mingw-w64— 通过MSYS2. 安装 Mingw-w64。 Mingw-w64 是 Windows 上流行的免费工具集。它提供了 GCC、Mingw-w64 和其他有用的 C++ 工具和库的最新本机构建。(MinGW-w64 和 CMake 安装完成可编译【opengl-win-demo】进行测试,双击文件内run.bat脚本
  • Python 3.12.0 — 在 Windows 上,从 Python 主页获取安装包。

相关文章补充打包示例和安装教程。

Windows下的 C++ 编译工具(MinGW-w64 + CMake) Windows 下的 OpenGL 开发环境配置(GLFW+GLAD) C/C++ for Visual Studio Code(建议第一次回车,第二次Y)

2.3 编译Emscripten

接下来,通过源码自己编译一个 Emscripten。运行下列命令来自动化地使用 Emscripten SDK。(在你想保存 Emscripten 的文件夹下运行),生成浏览器所需wasm文件

git clone https://github.com/juj/emsdk.git
cd emsdk
​
# 在 Linux 或者 Mac macOS 上
./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
./emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit
# 如果在你的 macos 上获得以下错误
Error: No tool or SDK found by name 'sdk-incoming-64bit'
# 请执行
./emsdk install latest
# 按照提示配置环境变量即可
./emsdk activate latest
​
​
# 在 Windows 上
emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit
​
# 注意:Windows 版本的 Visual Studio 2017 已经被支持,但需要在 emsdk install 需要追加 --vs2017 参数。

说明: emsdk install latest 安装过程中,由于安装依赖包需要从国外下载,所以会出现下载失败情况,比如链接storage.googleapis.com/webassembly…。具体解决办法参照文章WebAssembly(一)、环境配置,原理大致是:

  1. 修改emsdk中python代码,将需要下载的地址print打印到终端中,手动进行下载。
  2. 下载后将压缩包放到zips文件夹(需手动创建文件夹)中
  3. 再次执行命令emsdk install latest,工程会自动解压文件,安装到项目中,并且删除zips中对应压缩包文件。

emsdk activate --global --vs2017 latest 命令由于我没有使用 --vs2017 所以更改为emsdk activate --global latest来设置全局环境变量,如果不需要全局还可以去掉--global,如果环境变量没有自动添加成功,可以手动添加,或者在cmd中直接运行activate文件夹下 emsdk.bat activate 来激活,然后使用emcc。配置成功后环境变量截图。

image.png

安装成功后可以通过emcc -v 进行查看,如图所示:

image.png

可以通过双击build.sh启动脚本(内含emcc编译命令)对wasm-opengl-cube项目进行编译。

image.png

能够看到emcc编译后生成了wasm文件、js文件。

  • a.out.wasm 二进制的 wasm 模块代码
  • a.out.js 一个包含了用来在原生 C 函数和 JavaScript/wasm 之间转换的胶水代码的 JavaScript 文件
  • index.html 一个用来加载,编译,实例化你的 wasm 代码并且将它输出在浏览器显示上的一个 HTML 文件

image-20240426004445824.png

这时已经可以在html文件可以通过js文件和wasm文件,加载我们C++编译的图形了,但是由于浏览器跨域策略,需要在多一个步骤,在项目文件夹下,通过python启动一个http服务来进行访问。

输入命令:python3 -m http.server,http服务启动成功后可以在浏览器输入我们http://localhost:8000地址来进行访问,下图是我们编译的效果,后续将编译文件再放入到Electron中就可以了,因为electron的窗口内容也是通过加载html来渲染的,所以到这里通过单纯的html可以将C++编译后的wasm渲染到窗口上时,electron也一定是可以的。

C:\Users\user\Desktop\wasm-opengl-cube> python3 -m http.server
Serving HTTP on :: port 8000 (http://[::]:8000/) ...
::1 - - [26/Apr/2024 00:34:00] "GET / HTTP/1.1" 200 -
::1 - - [26/Apr/2024 00:34:00] "GET /a.out.js HTTP/1.1" 200 -
::1 - - [26/Apr/2024 00:34:00] "GET /a.out.wasm HTTP/1.1" 200 -
::1 - - [26/Apr/2024 00:34:01] code 404, message File not found
::1 - - [26/Apr/2024 00:34:01] "GET /favicon.ico HTTP/1.1" 404 -

image-20240426003424553.png

2.4 Electron集成WASM步骤

  1. 准备electron所需node环境
  2. 将编译好的a.out.wasma.out.jsindex.html文件放到electron根目录,electron的main.js文件加载htmlmainWindow.loadFile('index.html');作为窗口显示内容
  3. 启动electron项目

2.4.1. Electron环境准备

node版本 17.0.1

npm 安装 Electron 慢的解决方案 set ELECTRON_MIRROR="npm.taobao.org/mirrors/ele…" 项目中已经配置在.npmrc文件中

2.4.2. 将编译的文件放到Electron根目录

image-20240426140248213.png

2.4.3. Electron启动项目

打开electron文件夹npm i安装依赖

// package.json
{
  "name": "electron-cpp-opengl-demo",
  "version": "1.0.0",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "dependencies": {
    "electron": "21.3.0"
  }
}

依赖安装成功后npm start 启动项目,启动成功后效果如图

image-20240426141402338.png

2.5 WASM + OpenGL + C++ 其它优秀实践

wasm 初探,写个 Hello World

编译 C/C++ 为 WebAssembly

filament手机端3D图形渲染

Bevy 游戏引擎

3. C++编译技术方案—node-ffi/node-addon-api(未论证)

Electron不直接支持OpenGL,但是可以通过嵌入原生的C++代码来实现对OpenGL的支持。可以使用node-gyp工具将原生代码打包到Electron应用程序中,并通过使用node-ffi或node-addon-api绑定来访问OpenGL API。

考虑两方面:

一、现有技术方案较少,落地实施完成论证需求技术难度大。

二、使用node-gyp工具对C++代码写法侵入很大,需要引入#include <napi.h> 库,C++开发中需要兼容Node V8引擎,以下为示例代码。

// main.cc
#include <napi.h>
#include "calls.hh"
​
Napi::Object Init(Napi::Env env, Napi::Object exports) {
    // Setup Calls
    // glAccum
    exports.Set(Napi::String::New(env, "glAccum"), Napi::Function::New(env, _glAccum));
    // glAlphaFunc
    exports.Set(Napi::String::New(env, "glAlphaFunc"), Napi::Function::New(env, _glAlphaFunc));
    // glAreTexturesResident
    exports.Set(Napi::String::New(env, "glAreTexturesResident"), Napi::Function::New(env, _glAreTexturesResident));
    // glArrayElement
    exports.Set(Napi::String::New(env, "glArrayElement"), Napi::Function::New(env, _glArrayElement));
    // glBegin
    exports.Set(Napi::String::New(env, "glBegin"), Napi::Function::New(env, _glBegin));
    // glEnd
    exports.Set(Napi::String::New(env, "glEnd"), Napi::Function::New(env, _glEnd));

对于 Electron 应用来说,渲染进程中的内容主要通过 HTML、CSS 和 JavaScript 来展示和操作,而 C++ 中绘制的图形无法直接显示到 Electron 的渲染进程中。因此,最常见的做法是将 C++ 绘制的图形渲染到一个 Canvas 元素上,然后在 Electron 的渲染进程中显示这个 Canvas。

具体步骤如下:

  1. 编写 C++ 模块: 使用 C++ 编写绘制图形的模块,并将其编译为 .node 文件,以便在 Electron 中调用。
  2. 在 Electron 中调用 C++ 模块: 在 Electron 的主进程中调用 C++ 模块,并将绘制的图形数据传递给渲染进程。
  3. 在渲染进程中显示图形: 在渲染进程中通过 Canvas 元素接收并显示 C++ 模块绘制的图形数据。

相关文档,node-gyp和electron会出现NODE_MODULE_VERSION不一致情况,不是真实node版本,无法通过安装解决,需要通过命令手动指定版本编译解决,比如node-gyp rebuild --target=21.3.0 --arch=ia32 --dist-url=https://electronjs.org/headers

【1】electron程序,如何理解NODE_MODULE_VERSION?

【2】Electron和node版本差NODE_MODULE_VERSION不一致

【3】NODE_MODULE_VERSION对照表

【4】Node.js 和 C++ 之间的类型转换

4. 相关文档

【1】win搭建C++开发环境(gcc和cmake)

【2】高级前端进阶:我是如何把 C/C++ 代码跑在浏览器上的?

【3】图形编程接口——OpenGL(基础概念)

【4】Windows 下的 OpenGL 开发环境配置(GLFW+GLAD)