WebGL-Native: 脱 require("weak-napi");
prev: https://zenn.dev/okuoku/scraps/f7364055268608
next: https://zenn.dev/okuoku/scraps/fdfaff96dbf16f
GCによるnative objectの自動解放の実現
WebGLでは、GPU側に作成されたテクスチャ等のオブジェクトに対する参照がなくなったときに自動解放する必要がある。今のところ、これは weak-napi
モジュールを使用して実装している。
基本的には、このモジュールがやっていることを自前で実装すれば同等の機能を実現できると考えられる。
wrap_pointer
Node.jsの拡張APIであるN-APIには、ArrayBufferを作成するAPI napi_create_external_arraybuffer
が最初から解放コールバックを受けいれるため、 素のポインタを受けとって解放コールバック付きArrayBufferを返却する APIを実装してみた。
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はポインタを使っているので問題ない )
テスト
簡単なラッパを用意してテストしてみた。
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
なりなんなりを使う必要がある点 。
なんかめっちゃクネクネしてるな。。一般には、 malloc
→ free
してもOSから見えるメモリは解放されるとは限らないがVisualStudio的には通常見える。Node.jsはGCである程度まとめてオブジェクトを解放するようだ。
たまにクラッシュする
実際にコレを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の作りかたが悪いのかな。。
ArrayBufferをexternalに替えたら治った
問題は、JavaScript側に対してopaqueになっちゃう事だな。。まぁ通常のシチュエーションでは、APIのハンドルを実際にポインタとして読み書きすることは非常に稀なのでどうでも良いけど。