Open14

WebGL-Native: Linux移植

okuokuokuoku

直接ビルドに対応する

以前一旦LLVMを経由してビルドするような機能を追加 してるけど、gccで直接ビルドするならそういう配慮は不要なので直接ビルドできるようにした。

https://github.com/okuoku/cwgl-proto/commit/2e73d4742598db753c695e895b85218089b6a760

CMakeには仮想的なライブラリとしてオブジェクト(.o)を直接表現する OBJECT ライブラリがあるのでそれを使用している。

FAILED: libappdll_app.so
: && /usr/bin/cc -fPIC -O2 -g -DNDEBUG   -shared -Wl,-soname,libappdll_app.so -o libappdll_app.so CMakeFiles/app_wasm.dir/app_wasm.c.o CMakeFiles/appdll_app.dir/app_stub.c.o CMakeFiles/appdll_app.dir/home/oku/repos/cwgl/wasmstub/rt.c.o   && :
/usr/bin/ld: CMakeFiles/app_wasm.dir/app_wasm.c.o: relocation R_X86_64_PC32 against symbol `wasm_rt_call_stack_depth' can not be used when making a shared object。 -fPIC を付けて再コンパイルしてください。
/usr/bin/ld: 最終リンクに失敗しました: bad value
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.

... 必要なツールチェーンオプションが渡ってこないので直接 .c をビルドする形に変更した。

https://github.com/okuoku/cwgl-proto/commit/57a1320230bec715d2632d5acefd27fc098ad478

okuokuokuoku

NULL が無い

/home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c: In function ‘stub_library_get_import’:
/home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:357:25: error: ‘NULL’ undeclared (first use in this function)
  357 |     const char* name0 = NULL;
      |                         ^~~~
/home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:357:25: note: ‘NULL’ is defined in header ‘<stddef.h>’; did you forget to ‘#include <stddef.h>’?

... そうなのか。。

https://github.com/okuoku/cwgl-proto/commit/c54a1833fded493f6161ebd23688568bab298219

ヘッダをincludeしたときに定義されるシンボルは割とC標準ライブラリ(libc)ごとに個性がある。なのでLinuxやCygwinで正常にビルドできてもBSDを壊したりするというかwabtで自分のパッチもやってしまっている。

https://github.com/WebAssembly/wabt/pull/1360

(まぁこれはOpenBSDのlibcが悪いと思うけど...)

okuokuokuoku

mallocfree という関数がある

[1/2] Building C object CMakeFiles/appdll_app.dir/app_wasm.c.o
app_wasm.c:3051:12: warning: conflicting types for built-in function ‘malloc’; expected ‘void *(long unsigned int)’ [-Wbuiltin-declaration-mismatch]
 3051 | static u32 malloc(u32);
      |            ^~~~~~
app_wasm.c:5:1: note: ‘malloc’ is declared in header ‘<stdlib.h>’
    4 | #include "app_wasm.h"
  +++ |+#include <stdlib.h>
    5 | #define UNLIKELY(x) __builtin_expect(!!(x), 0)
app_wasm.c:3052:13: warning: conflicting types for built-in function ‘free’; expected ‘void(void *)’ [-Wbuiltin-declaration-mismatch]
 3052 | static void free(u32);
      |             ^~~~
app_wasm.c:3052:13: note: ‘free’ is declared in header ‘<stdlib.h>’
[2/2] Linking C shared library libappdll_app.so

そりゃあるでしょうね。。とりあえず fno-builtin を付ける。

https://github.com/okuoku/cwgl-proto/commit/43830885407ca69abbd24d7976490d41226ef789

実際には、これはUbuntuに入っている wasm2c が古いだけで、最新のものではシンボルに w2c_ を前置することでWASM内のシンボル名がビルドに影響しないようになっている。

https://github.com/WebAssembly/wabt/pull/1427

okuokuokuoku

エクスポートするシンボルの指定

/home/oku/repos/cwgl/wasmstub/rt.c: In function ‘__declspec’:
/home/oku/repos/cwgl/wasmstub/rt.c:282:51: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘{’ token
  282 | the_module_root(const uint64_t* in, uint64_t* out){
      |                                                   ^
/home/oku/repos/cwgl/wasmstub/rt.c:280:1: warning: type of ‘dllexport’ defaults to ‘int’ [-Wimplicit-int]
  280 | __declspec(dllexport)
      | ^~~~~~~~~~
/home/oku/repos/cwgl/wasmstub/rt.c:352: error: expected ‘{’ at end of input
  352 |
      |

まぁ __attribute__ ((visibility ("default"))) で良い。

https://github.com/okuoku/cwgl-proto/commit/dda5dff16f3fa2339e6ac272bd6d1363d0eeed26

dllexportやvisibilityは共有ライブラリが外部にエクスポートするシンボルを指定する。最近はデフォルトをhidden(エクスポートしない)に指定するのが普通なのでdefaultってどっちだよ感は有るが。。

okuokuokuoku

const 性要求が厳しい

[1/5] Building CXX object CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o
FAILED: CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o
/usr/bin/c++  -DCWGL_DLL -DCWGL_SHARED_BUILD -DYFRM_DLL -DYFRM_SHARED_BUILD -Dcwgl_EXPORTS -I/home/oku/repos/cwgl/include -I/home/oku/repos/cwgl/angle/include -I/usr/include/SDL2 -O2 -g -DNDEBUG -fPIC   -std=gnu++17 -MD -MT CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o -MF CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o.d -o CMakeFiles/cwgl.dir/yfrm/src-cxx17/yfrm-fs-cxx17.cpp.o -c /home/oku/repos/cwgl/yfrm/src-cxx17/yfrm-fs-cxx17.cpp
/home/oku/repos/cwgl/yfrm/src-cxx17/yfrm-fs-cxx17.cpp: In function ‘int yfrm_file_pathinfo(const char*, uint64_t*, uint64_t*, uint64_t*, uint64_t*)’:
/home/oku/repos/cwgl/yfrm/src-cxx17/yfrm-fs-cxx17.cpp:102:36: error: cannot bind non-const lvalue reference of type ‘std::filesystem::__cxx11::path&’ to an rvalue of type ‘std::filesystem::__cxx11::path’
  102 |     return file_info_gen(fs::u8path(path), flags, size, time_create, time_mod);
      |                          ~~~~~~~~~~^~~~~~
/home/oku/repos/cwgl/yfrm/src-cxx17/yfrm-fs-cxx17.cpp:65:38: note:   initializing argument 1 of ‘int file_info_gen(std::filesystem::__cxx11::path&, uint64_t*, uint64_t*, uint64_t*, uint64_t*)’
   65 | file_info_gen(std::filesystem::path& path,
      |               ~~~~~~~~~~~~~~~~~~~~~~~^~~~
[2/5] Building C object ncccstubs/CMakeFiles/yfrm_stubs.dir/colroot.c.o
ninja: build stopped: subcommand failed.

これもなんでVisualStudioで通るのか謎だな。。

https://github.com/okuoku/cwgl-proto/commit/5782119f113b7693d2c78fa574e674ae0bb4586c

okuokuokuoku

絵が出ない

... ここまでで起動するようになったが、 "Development Build" の文字しか描画されない。

これデバッグすんのか。。VirtualBoxで動かしているけど3Dアクセラレーションは切っているので、Mesaのソフトウェアレンダラで動作しているはず。

テクスチャや頂点は正常にアップロードされているのをRenderDocで確認できたので、例の如くラスタライザの設定が不味そう。

okuokuokuoku

API Validationでエラーが見つかった

RenderDocにもAPI validation機能があったのを思い出したので掛けてみたら速攻で問題が見つかった。

Depth-Stencil attachmentにしないとダメなのか。。この制約はGLES3で追加されたらしい。

https://github.com/Igalia/mesa/blob/5f4d9b419a1c931ad468b8b22b8a95b1216891e4/src/mesa/main/fbobject.c#L1388-L1400

   /* The OpenGL ES3 spec, in chapter 9.4. FRAMEBUFFER COMPLETENESS, says:
    *
    *    "Depth and stencil attachments, if present, are the same image."
    *
    * This restriction is not present in the OpenGL ES2 spec.
    */
   if (_mesa_is_gles3(ctx) &&
       has_stencil_attachment && has_depth_attachment &&
       !_mesa_has_depthstencil_combined(fb)) {
      fb->_Status = GL_FRAMEBUFFER_UNSUPPORTED;
      fbo_incomplete(ctx, "Depth and stencil attachments must be the same image", -1);
      return;
   }

これはWebGLも一緒なので、バックエンドがdepth-stencilバッファを作成できる( https://www.khronos.org/registry/OpenGL/extensions/OES/OES_packed_depth_stencil.txt )ならそっちを使うようにするか、そもそもGLES2コンテキストになるように頑張るかのどっちかが必要かな。。

ただ、MESAの MESA_GLES_VERSION_OVERRIDE を設定するとRenderDocが正常にキャプチャできなくなってしまった。

... というか今気付いたけど、これそもそもRaspberry PiのVC4だとどうやっても動かないような。。(Stencil8 + Depth16を書く方法が無い気がする)

okuokuokuoku

コードサイズがでかすぎる

もちろんRaspberry Pi上でもコンパイルできないので、Ubuntu上にクロスコンパイラを導入して wasm2c 部分だけをビルドすることにした。

sudo apt-get install g++-arm-linux-gnueabihf

EDIT: そもそもUbuntuはARMv6をターゲットできない (のでこのコンパイラも使えない)

wasm2c の出力コードについては C 以外の依存関係は一切ないので追加のライブラリは不要でビルドできるが、出力が大きすぎてビルドに失敗してしまった。。

/tmp/cceyILfN.s:40419112: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419125: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419138: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419151: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419164: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419177: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:40419191: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:41433611: Error: Thumb2 branch out of range
/tmp/cceyILfN.s:41433613: Error: Thumb2 branch out of range

そして -mlong_calls 付けても効かない。。(PICのPLT経由ジャンプは常に bl なので ← バグ..?)

静的リンクするしかないかな。RaspberryPi OSは幸いPIEは使っていないようだ。

pi@raspberrypi:~/build/cwgl/duk-nccc $ hardening-check yuniduk
yuniduk:
 Position Independent Executable: no, normal executable!
 Stack protected: no, not found!
 Fortify Source functions: no, only unprotected functions found!
 Read-only relocations: yes
 Immediate binding: no, not found!
okuokuokuoku

Raspberry Pi 1 はそもそもThumb-2に対応していない

なんか正しくビルドしたつもりのツールチェーンでも

sorry, unimplemented: Thumb-1 hard-float VFP ABI

みたいにエラーになったり、ビルドした .so をロードしたら SIGILL になったりと変だとは思ってたけど、 Raspberry Pi 1はARMv6なのでThumb-2に対応していない 。(実はThumb-2に対応したarmv6t2も無くは無いけどRPiは違う)

00000a90 <register_tm_clones>:
     a90:       480a            ldr     r0, [pc, #40]   ; (abc <register_tm_clones+0x2c>)
     a92:       490b            ldr     r1, [pc, #44]   ; (ac0 <register_tm_clones+0x30>)
     a94:       4478            add     r0, pc
     a96:       4479            add     r1, pc
     a98:       1a0b            subs    r3, r1, r0
     a9a:       0fd9            lsrs    r1, r3, #31
     a9c:       4a09            ldr     r2, [pc, #36]   ; (ac4 <register_tm_clones+0x34>)
     a9e:       eb01 01a3       add.w   r1, r1, r3, asr #2 ★★ ここで SIGILLになる
     aa2:       1049            asrs    r1, r1, #1
     aa4:       447a            add     r2, pc
     aa6:       b082            sub     sp, #8
     aa8:       d005            beq.n   ab6 <register_tm_clones+0x26>
     aaa:       4b07            ldr     r3, [pc, #28]   ; (ac8 <register_tm_clones+0x38>)
     aac:       58d3            ldr     r3, [r2, r3]
     aae:       9301            str     r3, [sp, #4]
     ab0:       b10b            cbz     r3, ab6 <register_tm_clones+0x26>
     ab2:       b002            add     sp, #8
     ab4:       4718            bx      r3
     ab6:       b002            add     sp, #8
     ab8:       4770            bx      lr
     aba:       bf00            nop
     abc:       016b65c8        .word   0x016b65c8
     ac0:       016b65c6        .word   0x016b65c6
     ac4:       016b6558        .word   0x016b6558
     ac8:       00000054        .word   0x00000054

... ツールチェーンをリビルドして仕切りなおし。そもそも Thumb-2 が無いならThumbに拘る理由もないし。。 (素のThumbではそもそも整数演算しかできない)

okuokuokuoku

8分掛けても起動しない

というわけで動くようにはなったけど、Unityのエンジン側の起動から8分経ってもシーンのロードが終わらない。。

$ ulimit -c unlimited
(再現)
$ kill -SIGQUIT 対象PID

でコアダンプを取ってバックトレースを見てみる。 gdb -c core でコアダンプにアタッチして file <ELF名>sharedlibrary でシンボルをロード。(メモリ不足でgdbと共存できない)

(gdb) sharedlibrary
(gdb) bt
#0  0x00023240 in duk__js_execute_bytecode_inner (
    entry_thread=<optimized out>, entry_act=<optimized out>)
    at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:3290
#1  duk_js_execute_bytecode (exec_thr=0x0, exec_thr@entry=0x1132918)
    at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:2960
#2  0x00019954 in duk__handle_call_raw (thr=thr@entry=0x1132918, idx_func=5,
    call_flags=call_flags@entry=0)
    at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2246
#3  0x0001a60c in duk_handle_call_unprotected (thr=thr@entry=0x1132918,
    idx_func=<optimized out>, call_flags=call_flags@entry=0)
    at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2422
#4  0x000175b4 in duk_call (thr=thr@entry=0x1132918, nargs=nargs@entry=2)
    at /home/pi/repos/cwgl/duktape260/duk_api_call.c:137
#5  0x000156b0 in nccc_cb_dispatcher (in=<optimized out>, out=0xbedced88)
    at /home/pi/repos/cwgl/duk-nccc/duk-nccc.c:337
#6  0xb5b39134 in instub_Z_envZ____syscall146Z_iii (arg0=<optimized out>,
    arg1=<optimized out>)
    at /home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:254
#7  0xb59643f4 in w2c____stdio_write (w2c_p0=w2c_p0@entry=1047420,
    w2c_p1=2347104, w2c_p1@entry=1183953, w2c_p2=w2c_p2@entry=12)
    at /home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:196093
#8  0xb5964618 in w2c____stdout_write (w2c_p0=w2c_p0@entry=1047420,
    w2c_p1=w2c_p1@entry=1183953, w2c_p2=w2c_p2@entry=12) at app2_wasm.c:7108113
#9  0xb59663d8 in w2c____fwritex (w2c_p0=w2c_p0@entry=1183953, w2c_p1=12,
    w2c_p1@entry=376, w2c_p2=w2c_p2@entry=1047420) at app2_wasm.c:7078348
#10 0xb59664cc in w2c__out (w2c_p0=w2c_p0@entry=1047420,
    w2c_p1=w2c_p1@entry=1183953, w2c_p2=w2c_p2@entry=376)
    at app2_wasm.c:7075304
#11 0xb59667d4 in w2c__printf_core (w2c_p0=w2c_p0@entry=1047420, w2c_p1=376,
    w2c_p2=<optimized out>, w2c_p3=<optimized out>, w2c_p4=<optimized out>,
    w2c_p4@entry=2346944) at app2_wasm.c:7120699
#12 0xb5968a78 in w2c__vfprintf (w2c_p0=1047420, w2c_p1=w2c_p1@entry=1183936,
    w2c_p2=0, w2c_p2@entry=2346768) at app2_wasm.c:7127712
#13 0xb5971e74 in w2c__printf (w2c_p0=1183936, w2c_p1=2346688)
    at app2_wasm.c:7240836
#14 0xb416269c in w2c___ZN11ContextGLES6CreateEi (w2c_p0=2)
    at app2_wasm.c:1265361
#15 0xb416294c in w2c___ZN13GfxDeviceGLES4InitE16GfxDeviceLevelGL (
    w2c_p0=860188, w2c_p1=1) at app2_wasm.c:1264672
#16 0xb44a0a24 in w2c___Z19CreateGLESGfxDevice17GfxDeviceRenderer (
    w2c_p0=19694464) at app2_wasm.c:2415453
#17 0xb5b83ab0 in w2c___Z19InitializeGfxDevicev () at app2_wasm.c:2153961
#18 w2c___Z24InitializeEngineGraphicsb (w2c_p0=0, w2c_p0=0)
    at app2_wasm.c:16061
#19 w2c___Z24PlayerInitEngineGraphicsb.constprop.0 (w2c_p0=0, w2c_p0=0)
    at app2_wasm.c:11265
#20 0xb44598fc in w2c___Z15InitWebGLPlayeriPPc (w2c_p1=18032984,
    w2c_p0=<optimized out>) at app2_wasm.c:2030441
#21 w2c__main (w2c_p0=1, w2c_p1=2346064) at app2_wasm.c:9571
#22 0xb5b43e28 in nccc_Z__mainZ_iii (in=<optimized out>, out=0xbedcf1f0)
    at /home/oku/repos/cwgl/apps/../wasmstub/stub.inc.c:255
#23 0x00015324 in nccc_call_trampoline (ctx=0x1132918)
    at /home/pi/repos/cwgl/duk-nccc/duk-nccc.c:232
#24 0x00019a80 in duk__handle_call_raw (thr=thr@entry=0x1132918,
    idx_func=idx_func@entry=1, call_flags=call_flags@entry=9)
    at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2268
#25 0x0001a60c in duk_handle_call_unprotected (thr=thr@entry=0x1132918,
    idx_func=idx_func@entry=1, call_flags=call_flags@entry=9)
    at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2422
#26 0x00023c84 in duk__executor_handle_call (call_flags=9,
    nargs=<optimized out>, idx=1, thr=<optimized out>)
    at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:2685
#27 duk__js_execute_bytecode_inner (entry_thread=<optimized out>,
    entry_act=<optimized out>)
    at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:4776
#28 duk_js_execute_bytecode (exec_thr=0xe3, exec_thr@entry=0x1132918)
    at /home/pi/repos/cwgl/duktape260/duk_js_executor.c:2960
#29 0x00019954 in duk__handle_call_raw (thr=thr@entry=0x1132918, idx_func=1,
    call_flags=call_flags@entry=0)
    at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2246
#30 0x0001a60c in duk_handle_call_unprotected (thr=thr@entry=0x1132918,
    idx_func=<optimized out>, call_flags=call_flags@entry=0)
    at /home/pi/repos/cwgl/duktape260/duk_js_call.c:2422
--Type <RET> for more, q to quit, c to continue without paging--c
#31 0x000175b4 in duk_call (thr=thr@entry=0x1132918, nargs=nargs@entry=0) at /home/pi/repos/cwgl/duktape260/duk_api_call.c:137
#32 0x00014844 in dukload (filename=0x4eb00 "/home/pi/repos/cwgl/duk-nccc/bootstrap.js", flags=8, ctx=0x1132918) at /home/pi/repos/cwgl/duk-nccc/yuniduk.c:72
#33 main (argc=<optimized out>, argv=<optimized out>) at /home/pi/repos/cwgl/duk-nccc/yuniduk.c:91

... OpenGL Context の生成まで進んでたの。。?確かUnityは起動時にVendor名とかをダンプするから、それに変なバイトが混じったりしたのかな。

okuokuokuoku

CreateShaderに失敗する

どうも glCreateShader音もなく 失敗してしまっているようだ。OpenGL的なエラー等をセットすることもなく、サイレントにゼロを返却してくる。

Raspberry PiのOpenGLスタックはソースコードが公開されているので、一旦それを適当にビルドしてstatic linkしてgdbで見てみることにした。

Thread 1 "yuniduk" hit Breakpoint 1, glCreateShader (type=35633)
    at /home/pi/repos/userland/interface/khronos/glxx/glxx_client.c:900
900        CLIENT_THREAD_STATE_T *thread = CLIENT_GET_THREAD_STATE();
(gdb) next
901        if (IS_OPENGLES_20(thread)) {
(gdb) p *thread
$1 = {error = 12288, bound_api = 12448, opengl = {context = 0x0, draw = 0x0,
    read = 0x0}, openvg = {context = 0x0, draw = 0x0, read = 0x0},
  high_priority = false,
  merge_buffer = "\b@\000\000N\005", '\000' <repeats 31 times>, "\371\377x\231+\000\000\000\371\377\350\256+\000\000\000\371\377\230D\032\000\000\000\371\377\250\313&\000\000\000\371\377\210\222\033\000\000\000\371\377h3\036\000\000\000\371\377\200\205\036\000\000\000\371\377\320\020&\000\000\000\371\377\350\304\033\000\000\000\371\377\070\306\033\000\000\000\371\377\210\307\033\000\000\000\371\377\340\306&\000\000\000\371\377\060\310&\000\000\000\371\377\200\311&\000\000\000\371\377`\372\017\000\000\000\371\377\260\373\017\000\000\000\371\377\220\375\017\000\000\000\371\377\030\231)\000\000\000\371\377h\232)\000\000\000\371\377\270\233)\000\000\000\371\377H"..., merge_pos = 36, merge_end = 36,
  glgeterror_hack = 0, async_error_notification = false}
(gdb) next
908        return 0;

https://github.com/raspberrypi/userland/blob/4a0a19b88b43e48c6b51b526b9378289fb712a4c/interface/khronos/glxx/glxx_client.c#L898-L909

threadopengl.context0 なのが不味いようだ。これを普段セットしているところを探すのが良いかな。

https://github.com/raspberrypi/userland/blob/e432bc3400401064e2d8affa5d1454aac2cf4a00/interface/khronos/egl/egl_client_context.c#L152-L175

... ここにstep inしようとしてローカルでビルドしたコードで統一したら正常に動作してしまった。。つまり、RaspberryPi OS標準配布のBroadcomスタックに何か問題があるということか。。ちょっと保留。