从零开始在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 ,这里提供一个简要的步骤概述:
-
将 OpenGL 代码编译为 WebAssembly:
- 使用 Emscripten 工具链将 C++ 代码编译为 WebAssembly。Emscripten 是一个工具链,可以将 C/C++ 代码编译为 WebAssembly 格式。
- 安装 Emscripten 并使用
emcc命令来编译你的 OpenGL C++ 代码。
-
Electron加载 HTML 页面:
- 在 HTML 页面中创建一个 Canvas 元素,用于渲染 WebGL 或者 WebAssembly 渲染的图形。
- 在页面中添加 JavaScript 代码,用于加载和运行编译后的 WebAssembly 模块,并在 Canvas 中渲染图形。
-
加载和运行 WebAssembly 模块:
- 在 JavaScript 中使用 WebAssembly 的相关 API 来加载和实例化编译后的 WebAssembly 模块。
- 编写 JavaScript 代码来与 WebAssembly 模块进行交互,比如传递数据和调用函数。
-
渲染图形到 Canvas:
- 在 WebAssembly 模块中编写 OpenGL 相关的渲染代码,包括顶点和片段着色器,以及渲染循环等。
- 在 JavaScript 中编写逻辑来将 WebAssembly 模块渲染的图形绘制到 Canvas 上。
Probably the eventual workflow when developing with WebAssembly:
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(一)、环境配置,原理大致是:
- 修改emsdk中python代码,将需要下载的地址print打印到终端中,手动进行下载。
- 下载后将压缩包放到
zips文件夹(需手动创建文件夹)中 - 再次执行命令emsdk install latest,工程会自动解压文件,安装到项目中,并且删除zips中对应压缩包文件。
emsdk activate --global --vs2017 latest 命令由于我没有使用 --vs2017 所以更改为emsdk activate --global latest来设置全局环境变量,如果不需要全局还可以去掉--global,如果环境变量没有自动添加成功,可以手动添加,或者在cmd中直接运行activate文件夹下 emsdk.bat activate 来激活,然后使用emcc。配置成功后环境变量截图。
安装成功后可以通过emcc -v 进行查看,如图所示:
可以通过双击build.sh启动脚本(内含emcc编译命令)对wasm-opengl-cube项目进行编译。
能够看到emcc编译后生成了wasm文件、js文件。
a.out.wasm二进制的 wasm 模块代码a.out.js一个包含了用来在原生 C 函数和 JavaScript/wasm 之间转换的胶水代码的 JavaScript 文件index.html一个用来加载,编译,实例化你的 wasm 代码并且将它输出在浏览器显示上的一个 HTML 文件
这时已经可以在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 -
2.4 Electron集成WASM步骤
- 准备electron所需node环境
- 将编译好的
a.out.wasm,a.out.js,index.html文件放到electron根目录,electron的main.js文件加载htmlmainWindow.loadFile('index.html');作为窗口显示内容 - 启动electron项目
2.4.1. Electron环境准备
node版本 17.0.1
npm 安装 Electron 慢的解决方案 set ELECTRON_MIRROR="npm.taobao.org/mirrors/ele…" 项目中已经配置在.npmrc文件中
2.4.2. 将编译的文件放到Electron根目录
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 启动项目,启动成功后效果如图
2.5 WASM + OpenGL + C++ 其它优秀实践
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。
具体步骤如下:
- 编写 C++ 模块: 使用 C++ 编写绘制图形的模块,并将其编译为
.node文件,以便在 Electron 中调用。 - 在 Electron 中调用 C++ 模块: 在 Electron 的主进程中调用 C++ 模块,并将绘制的图形数据传递给渲染进程。
- 在渲染进程中显示图形: 在渲染进程中通过 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不一致