GolangのOpenGL事情(WebGLも含むよ)

![スクリーンショット 2015-12-04 11.17.55.png](https://qiita-image-store.s3.amazonaws.com/0/6105/5474803b-d40c-e916-8330-357d39693cb5.png)

WebGLアドベントカレンダーでの投稿です。Golangのアドベントカレンダーはまた別に書きます。

最近、GolangでOpenGLを書いているのですが、GolangのOpenGL事情についての情報がまとまっているものがあまりなかったのでまとめてみます。

Golang用の各種OpenGL/WebGLライブラリ
====================================

由緒正しいライブラリgo-gl
---------------------------

Golangには昔からOpenGLのAPIを提供するgithubのOrganizationがあります。[go-gl](http://go-gl.github.io/)というコミュニティです。

<img width="859" alt="スクリーンショット 2015-11-30 0.13.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/6105/644a04d7-6ed5-7e59-0e38-ec9ef138662e.png">

昔からある多くのパッケージがここのライブラリを参照しています。OpenGLを管理する非営利団体のKhronosの提供するファイルを元にバインディングを生成しているため、安全確実な堅実なライブラリです。2.1以降の各OpenGL/OpenGL ESのバージョンに対応しています。また、OpenGLを使ったクロスプラットフォームなアプリケーションの土台として、glutの代わりに最近人気なglfwなんかもここでラッパーを提供しています。

WebGL
-------

Goには、JavaScriptを生成するgopher.jsという処理系があります。そのGopehr.jsでは、[WebGLのバインディング](https://github.com/gopherjs/webgl)を提供しています。

一通りの機能は提供しているように見えます。当然、ウェブ専用です。

Android/iOS
--------------

Golangは1.4からモバイル対応を徐々に加速させています。その中で一緒にリリースされているのが、 [golang.org/x/mobile]([https://godoc.org/golang.org/x/mobile)パッケージ群で、その中に、[OpenGL ES 2.0](https://godoc.org/golang.org/x/mobile/gl)なライブラリもあります。

goxjs/gl
------------

Golangは柔軟性が高い言語ではありますが、バックエンドの仕組みをまるっと入れ替える(しかもしれぞれがインタフェースを共有していない)のは多少骨が折れる作業です。C言語にあるような細かいブロックでON/OFFを切り替えるマクロがなく、より粒度の荒いファイルごとの条件コンパイルしかないため、差を吸収するのが大掛かりになりがちです。

そのような作業を代わりにやってくれているのが[goxjs/gl](https://github.com/goxjs)の提供するライブラリです。

<img width="1044" alt="スクリーンショット 2015-11-30 0.32.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/6105/6e45d74f-2ded-79df-c55b-3eba8f377441.png">

ここでは、OpenGL、GLFW、WebSocketのライブラリを提供しています。OpenGLに関してはgo-glの2.1へのラッパーという形で機能を提供しています。2.1というのはgo-glが対応している一番古いバージョン固定ですが、それとAPIバージョン(GLSLバージョンも)がほぼ同じ、OpenGL ES 2.0、Web GL 1.0へのクロスコンパイルに対応しています。

issueに上がっている議論で作者人が方針について説明しているのですが、Andoid/iOS向けのOpenGL ES実装とAPIの歩調を合わせつつ、同じAPIで、デスクトップのOpenGLやWebGLにもクロスコンパイルできるようなコードを提供しています。将来的にはAndoid/iOS向けのmobile/glとOpenGL ESとマージしたいとのこと。

なお、モバイル以外のデスクトップとウェブブラウザ版では、作者(アメリカ人？)が実際に動かして検証していないメソッドは、panicで堕ちるようになっていました。これは少し使いにくいので、一応ラッパーはすべて実装しつつ、未テスト状態のAPIであればログを出す方向で修正を出す方向で[議論](https://github.com/goxjs/gl/pull/15)してたりはします。もし、このような方針に変更になった場合(僕のPRがマージされた場合)、実際に使ってみた結果問題無さそうであれば、「きちんと動いているよ」報告をしてもらえるとみんなハッピーになるかと思います。

何を使うべき？
====================

さて、4つ選択肢がありましたが、どうやって選べばいいでしょうか？

まずgopher.js提供のWebGL単体ライブラリはあまり使う必要はないと思います。基本的に、ブラウザ「にも」提供したいと考えているのであれば、goxjs/gl一択になるでしょう。

モバイルのOpenGL ESに対応させるには、Android/iOS対応のmobile/glが一番検証もされていて今後も期待できるパッケージです。ARMでLinux/Darwinの場合はmobile/glを使ったコードを、そうじゃない場合はgoxjs/glを使ったコードがコンパイルできるように環境を整えて(条件ビルドのファイルを追加して)置くのが良いと思います。幸い、APIはほぼ同じはずなので、コンテキストを取得してくるあたり以外はほぼコピペで行けるでしょう。

ただ、WebGLはようやく1.0が普及し始めたという感じで、より機能が多いWeb GL 2.0が一般的な環境に入るまでは時間がかかるでしょう。goxjs/glは可搬性重視なので、OpenGLについては枯れたバージョン(2.1)を使うようになっています。もちろんハイエンドのAPIを駆使するなら、go-glの4系などを使うしかありません。

まとめると以下の様な感じです。

* goxjs/gl(+mobile/gl)

    とにかく可搬性重視。ブラウザがWebGL 2.0になるまではOpenGL3も、ES3も使わない気持ちを持っている人向け。mobile/glと両立できるかどうかは今後調べます。

* go-gl

    ブラウザで実行することはなく、最新を使いたい人。

* mobile/gl

    モバイル端末向けの開発を主に行い、今絶賛開発中のmobile系パッケージとの連係が最優先の人。

実際に書いてみたサンプルと既存のOpenGLコードを移植する時にハマったこと
========================================================================

https://shibukawa.github.io/nanovgo/demo/

C言語で書かれた、OpenGL用のベクタグラフィックスライブラリの[NanoVG](https://github.com/memononen/nanovg)のPure GO実装です。ベタ移植なので最適化はされてません。本来はfloat64相当の数値型しか持ってないJavaScriptに合わせて、数値はfloat64とintのどちらか以外使うべからず、というのがgopher.jsのサイトに書かれているPerformance Tipsですが、まずは動かすのを再優先にしてfloat32で実装してます。

Mac上で書いていますが、gopher.jsを使ってブラウザ向けでも動いていますし、当然Macでも動きますし、クロスコンパイルしてWindows 10でも動きました。

速度は最適化がされてないせいか、ブラウザは遅いです。MacBook Pro 2014(Core i7)のChromeで20fpsぐらい。Macネイティブビルドは200fps、あと、Macでも、外付けのRetinaじゃないディスプレイでは600fps〜800fpsは出ていて、行き過ぎた高解像度化の負の側面を垣間見ました。WindowsはCore i7 6700+GeForce GTX 970というスペックのおかげか2000fpsぐらい出てました。同じ値段で3D性能が段違いなので、MacからWindowsに回帰してくる人は増えてますよね。マウスコンピュータのLITTLEGEAR良いです。

まぁ今はベジェ曲線を三角ポリゴンに変換する処理などを全フレームで愚直にやっているので、変化がない場合は変換済みの座標をキャッシュしておくとかしたり、上記のfloat64を使うように改造すれば、WebGLでも60fpsは維持できそうな気がします。

なお、WebGLはパフォーマンスの制約か、ステンシルバッファなどのいくつかの機能がデフォルトでオフになっています。canvasタグからcontextを取得するメソッドの2番目の引数でオプションを渡して有効にしてあげる必要があります。思った通りの絵が出ない場合はそこを確認すると良いと思います。OpenGL 2.1からの移植でそれ以外に困ったことは今のところありません。goxjs/glfwでは当初これをオンにする手段が無かったので、[PR](https://github.com/goxjs/glfw/commit/ff14acdb9576e62b931d5d6cabaa4c6f4610a9a4)を出して入れてもらいました。

Golangで3D開発をする魅力
===========================

Gopher.jsを積極的に使っている例はそこまでまだ見てない気がします。まだ出てくるJSファイルが数MBあったりするせいとかもあると思いますが、僕個人としてはGolangを使ったWebの開発が今後一番メジャーな開発環境になるのでは、と思っています。

その一番大きな理由がWebAssemblyです。最初のターゲットの言語はC言語ですが、[Gopher.jsの中の人がWebAssemblyにも言及](http://www.gopherjs.org/blog/2015/09/28/surprises-in-gopherjs-performance/)してます。Gopher.jsがWebAssemblyに対応すればWebGLを使ったコードの実行(float32がネイティブ対応されるはず！)やロードが高速される見込みです。NanoVGoもフォント周りは自前でTrueTypeフォントをパースしてテクスチャに焼きこんでUVマッピングで使うという実装になっていますが、これもWebGLはアクセスできてもDOMへのアクセスが制限される(かもしれない)WebAssemblyのユースケースには合っているはず。

また、Gopher.jsはビルド時間がめっぽう早いです。今はNanoVGoのMac/Windows版はcgoを使ってCのライブラリをリンクしているため、ビルド時間は結構長いです。↑のサンプルコードで30秒以上かかってます(バッテリー駆動時)。一方、Gopher.jsの方はPure Go実装になっているため(cgoを使うとGopher.jsは腹を下す)、0.3秒ほどです。もう、他のJavaScriptでトランスパイラとかjslint/eslintとかの様々なツールを使う気が失せるほどです。普段の開発でもOpenGLを使う場合はGopher.jsでビルドしてテストしたほうが良いかな、と思うレベル。WebGL inspectorとか使えますし。Mac/Windowsのビルドの遅さは、1.5から機能追加されはじめた(まだLinuxのみサポート)の-sharedビルドオプションとかで解決したらいいなぁ、と思っています。来年の1.6に期待。

WebGL+Golangだけでも魅力はありますが、さらにネイティブで動かした時のパフォーマンスの高さと可搬性の高さもメリットです。速度的にはC言語版とGolangネイティブ版は差はありませんし(多少早い？)、メモリ消費量も9MB vs 33MBで、バイナリサイズも6MB程度(小さくするオプションを付けて)なので、まぁC言語には負けるけど他のマルチプラットフォームを唄う今どきのものよりは大分少ないと思います。AndroidやiOSといったモバイル対応も進んでいますし、これだけしか消費しなければRas Pi Zeroとかでも快適に動くんじゃないですかね？(試してないけど試したい)。

とりあえず、NanoVGo自体はシンプルでまだまだすごく便利、というところまでは到達はできていないのですが、JavaScriptの最新のES2015だとかBabelだとかWebPackだReactだFluxだとかを追い掛けるのに時間を使うのは他の人に任せて、僕はGolangにリソースを集中しているところです。既婚者は取捨選択が命。

明日はemadurandalさんです。