跳到主要内容

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

最简单示例

这里我们写一个最简单的示例代码:

main.cpp
#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.jsa.out.wasm两个文件。a.out.wasm包含编译完成的 C/C++ 代码,a.out.js 包含加载 wasm 文件的代码,和运行时支持。

在生成的 JavaScript 文件中,会使用到一个名为 Module 的全局对象,我们可以在运行该脚本时,提前定义好改对象,用以控制 wasm 的执行状态。如果我们指定编译输出文件的后缀为 .htmlem++ .\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_JSEM_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.htmlindex.js以及index.wasm文件,使用 emrun .\index.html 即可打开浏览器运行,看到中间出现一个黑色长方形,那就是SDL默认的初始状态。