SDL、WebAssembly 杂记
阅读量
0
阅读人次
0
SDL
SDL(Simple DirectMedia Layer)是一个跨平台开发库,旨在通过 OpenGL 和 Direct3D 提供对音频、键盘、鼠标、操纵杆和图形硬件的低级访问。可以在大部分的开源库看到它的身影,因为它体积小,且轻便。非常方面时候简单的跨平台开发,例如通过其在屏幕上显示画面。
例如 FFmpeg 项目中的 ffplay 就是使用其在窗口上显示视频画面的。这里我们简单的在 Windows 下写一个非常简单的示例。首先下载二进制库文件 SDL2-devel-2.30.1-VC.zip
。
在 Windows 下,我们需要定义宏 SDL_MAIN_HANDLED
(在SDL_main.h
中有简单的说明)。
#include <iostream>
#include <SDL.h>
constexpr int ScreenWidth = 640;
constexpr int ScreenHeight = 480;
int main(int argc, char const *argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) { //初始化 SDL
std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
return -1;
}
auto window = SDL_CreateWindow("SDL Demo", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, ScreenWidth,
ScreenHeight, SDL_WINDOW_SHOWN);
while (true) {
SDL_Event e;
SDL_PollEvent(&e);
if (e.type == SDL_QUIT) break;
}
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
这是一个最简单的示例,它会创建一个窗口,然后里面都是黑的。
libSDL2pp
libSDL2pp 为 SDL2 提供 C++ 绑定/包装器,这里仅提及,不做介绍。
WebAssembly
Emscripten 是一个完整的 WebAssembly 编译器工具链。最开始知道这项技术的时候,第一感觉就是 wasm 为 C/C++ 开发人员进入Web开发打开了一个新的方向。
最开始接触到的是 Qt for WebAssembly,当时以为这样就能够即使不会前端技术,也能在Web上开发APP了,但是实验下来,貌似情况不是这样的。一方面是 Qt for WebAssembly 实现上还不完善,存在着一些至少在我目前认为是莫名其妙的BUG。另外一个,打包出来的文件,依旧非常大,那么这就以为的在外网部署不太现实。只能在内网使用,或者是特定场合的重量级应用,使用者愿意花五六秒种去等待,也不在意流量。
我是在 lvgl 中看到 sdl 也能在 WebAssembly 中使用的,因为 lvgl 在 WebAssembly 中,也是使用 sdl 作为渲染接口。
环境搭建
需要先安装 Python。
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
Set-ExecutionPolicy -Scope CurrentUser remotesigned
.\emsdk.ps1 install latest
.\emsdk.ps1 activate latest
# Activate PATH and other environment variables in the current terminal
.\emsdk_env.ps1
最简单示例
这里我们写一个最简单的示例代码:
#include <emscripten.h>
#include <emscripten/bind.h>
#include <iostream>
#include <string>
int add(int a, int b) {
return a + b;
}
std::string add(const std::string &a, const std::string &b) {
return a + b;
}
EM_JS(int, javaScriptAdd, (int a, int b), {
console.log("add a+b in javascript.");
return a + b;
});
EMSCRIPTEN_BINDINGS(my_module) {
emscripten::function("add", emscripten::select_overload<int(int, int)>(&add));
emscripten::function("stringAdd",
emscripten::select_overload<std::string(const std::string &, const std::string &)>(&add));
}
int main(int argc, char const *argv[]) {
std::cout << "hello, emscripten!" << std::endl;
std::cout << "a+b=" << javaScriptAdd(5, 6) << std::endl;
int x = EM_ASM_INT({
console.log(`add ${$0} + ${$1} in inline javascript.`);
return $0 + $1;
}, 7, 8);
std::cout << "7+8=" << x << std::endl;
return 0;
}
然后我们编译:
em++ .\main.cpp
会生成 a.out.js
和a.out.wasm
两个文件。a.out.wasm
包含编译完成的 C/C++ 代码,a.out.js
包含加载 wasm 文件的代码,和运行时支持。
在生成的 JavaScript 文件中,会使用到一个名为 Module 的全局对象,我们可以在运行该脚本时,提前定义好改对象,用以控制 wasm 的执行状态。如果我们指定编译输出文件的后缀为 .html
:em++ .\main.cpp -o main.html
,那么除了上述两个文件,Emscripten 还会为我们生成一个 main.html
文件,这是 Emscripten 提供的一个默认文件,文件模板为 D:\emsdk\upstream\emscripten\src\shell.html
。
这里我们实现一个最近的模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Emscripten Demo</title>
</head>
<body>
<script>
var Module = {
print: function (text) {
console.log(text);
},
printErr: function (text) {
console.error(text);
},
onRuntimeInitialized: function () {
console.log("2+3=", Module.add(2, 3));
console.log("'2'+'3'='", Module.stringAdd('2', '3'));
}
};
</script>
{{{ SCRIPT }}}
<script>
</script>
</body>
</html>
em++ .\main.cpp -o main.html --shell-file home.html
emrun .\main.html --browser iexplore --kill_exit #这样 Ctrl+C 可以终止进程
上面的示例代码使用了 Embind 进行了 C++ 函数在 Javascript 中的绑定,它是一个很强大的库,提供了C++ 类,函数等绑定功能,也提供了引用 Javascript 对象的能力。如果没有它,仅在 Javascript 中仅仅调用 C API 就需要花费一番功夫。
基本每种解释语言与C交互的API很很复杂繁琐,但他们基本都有 C++ 利用其模板特性进而封装的各种绑定库,方便解释语言与C++之间的交互。例如 Python、Lua 这些都提供了类似的
C++ xxx binding
库。
另外,也展示了利用 EM_JS
、EM_ASM_INT
等宏,在 C++ 中调用执行 Javascript 代码的能力。
顺便提一句,在 wasm
被加载完成后,其会默认执行 main()
函数,我们可以将 Module
的成员变量 noInitialRun
改为 true
,跳过其执行。
使用SDL
WebAssembly 下的 SDL 代码示例和 上面的代码差不多,唯一要注意的是 main()
函数不能被阻塞,所以需要循环执行的函数需要在 Emscripten 提供的 emscripten_set_main_loop_arg()
下执行 。
以及注意 Emscripten 的编译方式,这里仅提供最简单的CMake配置:
set(CMAKE_EXECUTABLE_SUFFIX ".html")
list(APPEND CMAKE_C_FLAGS "-s USE_SDL=2")
list(APPEND CMAKE_CXX_FLAGS "-s USE_SDL=2")
add_executable(index main.cpp)
然后执行如下命令进行编译:
D:\emsdk\emsdk_env.ps1 # 编辑环境
emcmake cmake -S . -B build
cmake --build build --target all
就会在构建目录build
下生成index.html
、index.js
以及index.wasm
文件,使用 emrun .\index.html
即可打开浏览器运行,看到中间出现一个黑色长方形,那就是SDL默认的初始状态。