Open5

WebGL-Native: 脱 require("weak-napi");

okuokuokuoku

prev: https://zenn.dev/okuoku/scraps/f7364055268608
next: https://zenn.dev/okuoku/scraps/fdfaff96dbf16f

GCによるnative objectの自動解放の実現

WebGLでは、GPU側に作成されたテクスチャ等のオブジェクトに対する参照がなくなったときに自動解放する必要がある。今のところ、これは weak-napi モジュールを使用して実装している。

https://www.npmjs.com/package/weak-napi

基本的には、このモジュールがやっていることを自前で実装すれば同等の機能を実現できると考えられる。

okuokuokuoku

wrap_pointer

Node.jsの拡張APIであるN-APIには、ArrayBufferを作成するAPI napi_create_external_arraybuffer が最初から解放コールバックを受けいれるため、 素のポインタを受けとって解放コールバック付きArrayBufferを返却する APIを実装してみた。

https://github.com/okuoku/cwgl-proto/commit/9be7ac74e850191580a2ebc451625f172f7e7173

wrap_pointer は、ポインタ、サイズと NCCCのforward-1関数 を受けとって、 ArrayBufferオブジェクト externalオブジェクトを返却する。

[ptr dispatch ctx arg] => ptr

(↑ NCCC関数と同じ表記を採用しているが、 wrap_pointer はJavaScript関数として提供する)

解放用コールバックは、元のポインタおよびオブジェクトの作成時に渡されたコンテキスト arg をうけとって、元々の ptr を解放することが期待される。

[arg ptr] => []

コールバックとして直接JavaScriptのfunctionを受けとる形にしなかったのは、そもそもJavaScript側をGC中にコールバックできるとは限らないんじゃないかと思ったため。。ダメそうだったらFFIバインディングから直接解放用のforward-1関数を出すことにする。

N-APIがポインタ(ArrayBufferやexternal)に直接こういう機能を付けたのは上手いなと思った。基本的に解放用のコールバックが必要なのはネイティブのポインタに対してであり、他のobjectに対してコールバックを付けたいケースはあんまり無いんじゃないだろうか。(ネイティブ側を呼ぶ際のハンドルとしてはポインタが使われるのが普通なため -- OpenGLは例外的にハンドルとして int 型整数を使うため直接は使えないが、cwglはポインタを使っているので問題ない )

okuokuokuoku

テスト

https://github.com/okuoku/cwgl-proto/commit/672b6b8b2d40dd28860b93edc86c7aa22093e425

簡単なラッパを用意してテストしてみた。

const ncccutil = require("./ncccutil.js");

let i = 0;

function freeptr(addr){ // ★ スコープの問題は面倒なので、解放ルーチンはトップレベルに書く
    console.log("Freeing", addr);
    ncccutil.free(addr);
}

function runtest(){
    let ptr = null; // ★ この ptr のスコープは関数を抜けると外れる
    console.log("LOOP =",i);
    ptr = ncccutil.malloc(1024);
    ptr = ncccutil.wrapptr(ptr, 1, freeptr);
    i++;
    setImmediate(runtest);
}

setImmediate(runtest);

ポイントは、GCコールバックはある種のイベントであるため、 単純な無限ループではなく setImmediate なりなんなりを使う必要がある点

なんかめっちゃクネクネしてるな。。一般には、 mallocfree してもOSから見えるメモリは解放されるとは限らないがVisualStudio的には通常見える。Node.jsはGCである程度まとめてオブジェクトを解放するようだ。

okuokuokuoku

たまにクラッシュする

実際にコレをWebGL内で使ってみると、たまにクラッシュするようだ。。

#
# Fatal error in , line 0
# Check failed: result.second.
#
#
#
#FailureMessage Object: 000000F5ADCFCF40
 1: 00007FF7D87703DF napi_wrap+109311
 2: 00007FF7D86A4B7F std::basic_ostream<char,std::char_traits<char> >::operator<
<+57151
 3: 00007FF7D92DD602 V8_Fatal+162
 4: 00007FF7D8D814AD v8::internal::BackingStore::Reallocate+653
 5: 00007FF7D8FC72E9 v8::ArrayBuffer::GetBackingStore+137
 6: 00007FF7D87511CC napi_get_arraybuffer_info+76
 7: 00007FFBC0F7165D get_pointer+141 [C:\cygwin64\home\oku\repos\cwgl\node-nccc\
node-nccc.c]:L65
 8: 00007FFBC0F71A23 value_out+771 [C:\cygwin64\home\oku\repos\cwgl\node-nccc\no
de-nccc.c]:L160
 9: 00007FFBC0F72366 nccc_call_trampoline+998 [C:\cygwin64\home\oku\repos\cwgl\n
ode-nccc\node-nccc.c]:L360
10: 00007FF7D874CBD6 node::Stop+35046

napi_get_arraybuffer_info を呼ぶだけ でクラッシュしているので、external arraybufferの作りかたが悪いのかな。。