OpenGL を使うテストを CI でするときに osmesa が使えそう

はじめに

前に docker で OpenGL を使う環境を作ってそれでローカル環境では動かせていたんですが CI の環境では GPU が使えなかったり、X Server がない、privilegedのオプションが使えないなど場合もあって そのままでは使えないことが多いので CI でも使える方法を少し調べていました。

テストするときも実際に使うときと同じ環境でやったほうがいいのは間違いないですが、 CI である程度の部分だけでもテストできると嬉しいと思います。

osmesa

Linux 環境では OpenGL はほとんどの場合 GLX から使うようになっています。 同様に Windows の場合は WGL から OpenGL を使います。

GLW と WGL には OpenGL のコンテクストを作成したり、OpenGLAPI のアドレスを取得するインターフェースがあります。

Programming OpenGL in Linux: GLX and Xlib - OpenGL Wiki

CI 環境や GUI がインストールされていないサーバーだと GLX が使えない場合があります。 こうした場合に osmesa が使えるようです。osmesa はソフトウェアレンダリングによる OpenGL 実装です。

www.mesa3d.org

ただ osmesa は OpenGL の機能を完全に実装しているわけではないようで、新し目の glsl バージョンのシェーダをコンパイルできないことがありました。 なので描画に直接関係する部分のテストには使えないかなと思いますが、バッファの確保や解放などリソース管理のテストぐらいはある程度できると思います。

Ubuntu の場合は apt で osmesa を入れることができたので、これを使っています。 (apt のパッケージは libosmesa6-dev という名前でした。)

自分でビルドするともう少し新しい OpenGL バージョンに対応しているかもしれません。

LD_PRELOAD で関数を置き換える

osmesa を使うにはコンテキストを初期化する部分と OpenGL API のアドレスを取得する部分を変更する必要があります。

コンテキストを初期化する部分は独自に実装すればなんとかなりますが、 OpenGL API のアドレスを取得する部分が OpenGL バインディングライブラリ内にあって切り替えられないという問題がありました。

そこで LD_PRELOAD の環境変数を使って glXGetProcAddress を呼び出したときに OSMesaGetProcAddress が使われるようにするという方法を使いました。

動的リンクのライブラリに同じ名前の関数が複数あった場合に先に見つかった関数が使われるため、 LD_PRELOAD でライブラリを読み込ませることで上書きできるようです。

下のようなコードを書いて共有ライブラリを作成して使いました。

typedef unsigned char GLubyte;
typedef void (*genericFunctionPointer)(void);
extern genericFunctionPointer OSMesaGetProcAddress(const GLubyte *);

genericFunctionPointer glXGetProcAddress(const GLubyte* name)
{
    return OSMesaGetProcAddress((const char*)name);
}

genericFunctionPointer glXGetProcAddressARB(const GLubyte* name)
{
    return OSMesaGetProcAddress((const char*)name);
}
gcc -shared -fPIC cbits/glxoverride.c -o libglxoverride.so

GPU を使える場合

github.com

nvidia で公開されている docker コンテナでは GLX のかわりに EGL を使うようになっているようです。 GPU は使えるが X Server はないという場合は EGL が使えそうです。

CircleCI で GPU を使える環境もあるようで予算がある場合はこれもいいかもしれません。

https://circleci.com/docs/ja/2.0/gpu/