WebGL-Native: Node.jsから呼び出す準備
prev: https://zenn.dev/okuoku/scraps/9e2926081051f7
next: https://zenn.dev/okuoku/scraps/4529390fd6ff82
作戦
前回まででWebGLの各メソッドをC言語APIに開くことができたので、これを ffi-napi
とか fastcall
のようなモジュールを利用してNode.jsから呼ぶのが次の目標となる。
デプロイの容易性を考えるとN-APIを使用したモジュールの方が好ましいので今回は ffi-napi
を使うことになりそう。自動的にC側のオブジェクトを解放する際に必要になる弱参照は別モジュールの weak-napi
で提供されている。
というわけで、Node.js側のライブラリは揃ってそうなので、それ用のDLLを作るのが次の目標となる。
やること
- 絵出し 。そもそもOpenGL ES2のコードで絵が出てこないとどうしようもない。
-
コンテキスト作成APIの実装 。DOMを持たないので、
createElement
に相当するAPIは新規設計してやる必要がある。 - DLL化 。Shared libraryにして絵出しアプリと同等の描画をDLL呼び出しで得る。
とりあえず静的リンクで絵出し
APIトレースの都合で、PowerVRのOpenGLエミュレータを使った。システムのOpenGL ES2(IntelのオンボードGPUが提供するES2コンテキスト)ではEGLが使われないので、OpenGL ES用のツールではAPIトレースできない。 ... そしてIntelのツールはWindows版ではOpenGLをサポートしてない。Android版は有るのに。。
- https://github.com/okuoku/cwgl-proto/blob/1722caa8cd8daec383aa56e03d47f4fb22abc300/testapp.c#L60-L63
/* Draw something */
cwgl_viewport(NULL, 0, 0, w, h);
cwgl_clearColor(NULL, col, col, col, 1.0f);
cwgl_clear(NULL, 0x4000 /* COLOR BUFFER BIT */);
こんな感じのコードで描画ができた。実際には、これらの cwgl_
APIをJavaScript上のWebGLにwrapし、Node.jsから呼べるようにするのが最終目標となる。
APIトレースとかはarticleに分けた。
CMakeビルドへの統合
今回もビルドにはCMakeを使っている。
SDL2
今回はプラットフォーム抽象化レイヤとして一旦SDL2を使うことにした。使いなれてるし、後で置き換えるのも簡単なので。
SDL2はVulkan SDK付属のものを使用している。インストーラは環境変数 VK_SDK_PATH
を設定してくれるのでパスの選択にはそれが使える。(この変数は後方互換用なので新しいほうに置き換える ★)
file(TO_CMAKE_PATH $ENV{VK_SDK_PATH} vksdk)
if(EXISTS ${vksdk})
set(sdl2_inc ${vksdk}/Third-Party/Include/SDL2)
set(sdl2_lib ${vksdk}/Third-Party/Bin)
set(sdl2_dll ${vksdk}/Third-Party/Bin/SDL2.dll)
endif()
GLES2エミュレータ
ちょっとダサいがパス決めうちにした。PowerVRのサイトからPVRVFrameとPVRCarbonをダウンロードしてインストールしておく必要がある。(レジストリとか見るのが正攻法だと思うがどうせ一時的だし面倒なのでやらない)
file(TO_CMAKE_PATH "C:/Imagination Technologies/PowerVR_Graphics/PowerVR_Tools" pvrroot)
if(EXISTS ${pvrroot})
# PVRVFrame
set(emu_lib "${pvrroot}/PVRVFrame/Library/Windows_x86_64")
# PVRCarbon
set(es2cap_lib "${pvrroot}/PVRCarbon/Recorder/GLES/Windows_x86_64")
endif()
DLLのコピー
これらのDLLはPATHに入っていないので、POST_BUILD
イベントで適当にDLLをコピーしてやる。
add_custom_command(TARGET testapp
POST_BUILD
# Deploy SDL2.dll, GLES2
COMMAND ${CMAKE_COMMAND} -E copy
${sdl2_dll}
${emu_lib}/libEGL.dll
${emu_lib}/libGLESv2.dll
${CMAKE_CURRENT_BINARY_DIR}
)
Windows上のSDL2でOpenGL ES2エミュレータを使う
SDL2のデフォルトでは、ESコンテキストを要求してもデスクトップOpenGLのOpenGL ES2コンテキストを使用してしまうため、Hint SDL_HINT_OPENGL_ES_DRIVER
を設定してやる必要がある。
SDL_SetHint(SDL_HINT_OPENGL_ES_DRIVER, "1");
RenderDocでAPIトレースする
... RENDERDOC_HOOK_EGL=0
すれば普通にOpenGLレベルのトレースができた。。(SDLがEGLをwrapしてしまうため、素のキャプチャはうまくいかない)
じゃぁキャプチャの統合は要らないかな。。
DLL化
Win32のDLL(いわゆるshared library)としてC関数を公開するには、DLLのビルド時に __declspec(dllexport)
を付けてビルドし、DLLを使うアプリのビルド時は __declspec(dllimport)
を付けてビルドする必要がある。
このために、全てのC関数は最初から CWGL_API
マクロを先頭に付けて宣言してあった。
DLLを呼び出すコードはSDLやOpenGLの関数は一切呼ぶ必要がなくなる。
- https://github.com/okuoku/cwgl-proto/blob/f375814da299947f9d8a9f38a514d6b85450765e/testapp.c#L16-L29
for(;;){
cwgl_ctx_frame_begin(ctx);
float step = frame % 256;
float col = 1.0f * step / 256.0f;
/* Draw something */
cwgl_viewport(ctx, 0, 0, w, h);
cwgl_clearColor(ctx, col, col, col, 1.0f);
cwgl_clear(ctx, 0x4000 /* COLOR BUFFER BIT */);
cwgl_ctx_frame_end(ctx);
frame ++;
}
Node.jsへの移植
↑ の C コードをNode.jsに移植するとこのようになる:
for(;;){
let step = frame % 256;
let col = 1.0 * step / 256.0;
CWGL.cwgl_ctx_frame_begin(ctx);
CWGL.cwgl_viewport(ctx, 0, 0, w, h);
CWGL.cwgl_clearColor(ctx, col, col, col, 1.0);
CWGL.cwgl_clear(ctx, 0x4000 /* COLOR BUFFER BIT */);
CWGL.cwgl_ctx_frame_end(ctx);
frame++;
}
C言語へのブリッジ CWGL
は、npmのライブラリ ffi-napi
の手続きを使って生成できる。
// Types
const cwglCtx = REF.refType(REF.types.void);
const libdef = {
cwgl_init: [ "int", []],
cwgl_ctx_frame_begin: ["void", [cwglCtx]],
cwgl_ctx_frame_end: ["void", [cwglCtx]],
cwgl_ctx_create: [ cwglCtx, ["int","int","int","int"]],
cwgl_viewport: [ "void" , [cwglCtx, "int","int","int","int"]],
cwgl_clearColor: ["void", [cwglCtx, "float","float","float","float"]],
cwgl_clear: ["void", [cwglCtx, "int"]]
};
const CWGL = FFI.Library("../out/build/x64-Debug/cwgl.dll", libdef);
... 今 cwgl_
APIは165個あるが、それら全部について宣言を用意する必要がある。。
(最後の方は使わないからサボってるけど、) 気合で宣言を用意した。。これをWrapしてWebGL1実装にしていく。