EGLとOpenGLを使用するコードのビルド方法〜libGLからlibOpenGLへ
はじまりは以前書いたブログへの質問
カブクの甘いもの担当の高橋憲一です。
1年程前にこのカブクエンジニアブログで「GCPのGPUインスタンスでレンダリングを高速化」という記事を書きました。
EGLを使うことでX Serverを起動しなくてもサーバーサイドでGPUを活用した高速3Dレンダリングができるようにするという話で、現在は同僚の尽力によりGKEクラスタ上で実運用に載っており、カブクが提供しているサービスでは、3DモデルがアップロードされるとGPU搭載インスタンスでプレビュー画像が高速レンダリングされるようになっています。
…というように安定運用状態で安心していたところ、ブログ記事を読んだ方から「この通りにしても動かないのだけどどうしたら良いのでしょう」という質問が届きました。
現象としては、EGLの一連の手続きは問題なく実行されるもののglCheckFramebufferStatus()
にGL_FRAMEBUFFER
を渡して呼んだ際の戻り値がGL_FRAMEBUFFER_COMPLETE
にならない、すなわちOpenGLのフレームバッファオブジェクトの生成に失敗する…という状態でした。
原因の調査と問題の解決
詳細を調べてみると、eglGetError()やglGetError()でエラーの詳細を見ても全て正常終了となるのですが、glGetString(GL_VERSION)で取得できるはずの文字列はnullで返ってきます。
(正しく実行されると"4.5.0 NVIDIA 384.111"というような文字列が返ります)
全く同じコードが先方では動かず、カブクの手元の環境では正しく動くという状況だったため、両者の環境の差を比較すると、
カブク:
- GPU
- ローカルPC GTX980
- クラウド(GCP) K80
- OS: Ubuntu 16.04 LTS
- NVIDIAドライバのバージョン: 384
質問者:
- GPU
- ローカルPC GTX1060
- クラウド(AWS) K80
- OS: Ubuntu 16.04 LTS
- NVIDIAドライバのバージョン: 390
当初はGPUのアーキテクチャの差も疑ったのですが、GCPとAWSの違いがあるとは言えクラウド側はK80で同じですし、どうやらNVIDIAのGPUドライバのバージョンが異なるところに原因がありそうだということが分かりました。
現在運用で使用しているドライバのバージョンは384、質問してくれた方の環境は390ということで、カブクの環境より新しいバージョンが使われていました。
そこで最初に伝えた解決策としては「ドライバのバージョンを384にしてみてください」というもので、幾つかやり取りがあった後に「動きました!」という連絡を頂きました。
追加調査と衝撃の事実
動いたということで一安心…したかったのですが、どうもスッキリしません。
手元で安定運用できているとはいえ、いつまでも古いバージョンのドライバを使い続けるというのも気持ちが良いものではありませんし、新しいアーキテクチャのGPUが出てきた時に古いドライバでの動作はサポートされない可能性もあります。
そこで、あらためてGCP上に新しいGPUインスタンスを立てて真っさらな状態から試してみることにしました。Googleの公式解説の通りにドライバのセットアップを進めてみると、GPUドライバのバージョンは390となり、この環境で先ほどのコードを実行すると同様の現象が再現しました。
回避策や同様の現象の報告は無いのかあらためて調べてみると、
NVIDIAのDeveloper blogに「Linking OpenGL for Server-Side Rendering」という記事を見つけました。
対応としては、「リンクの際のライブラリとしてlibGL.soではなく、libOpenGL.soを指定しなさい」というものです。
これまでは
g++ test_egl.cc -o test_egl -lEGL -lGL
というようにしてlibEGL.soとlibGL.soをリンクするライブラリとして指定していました。
「え?いつの間にそんな話になっていたの?」と思いながら下記のようにしてlibEGL.soとlibOpenGL.soをリンクするように指定してビルドしてみると…
g++ test_egl.cc -o test_egl -lEGL -lOpenGL -L/usr/lib/nvidia-390
ドライバのバージョンが390の環境で実行しても無事に正しく動きました!
SGIのIRIXを使っていた古より、unix環境でOpenGLを使う時はリンクのオプションには -lGL を指定してきたものですが、あれから早20数年。。。NVIDIAのブログ記事には “This post is about how to correctly link a modern OpenGL application.“とも書いてあり、どうやら私はモダンなOpenGL使いではなかったようで、古い習慣を改める時が来たようです(^^;
このような仕組みが出てきた経緯を同ブログから紐解いてみると、
OpenGL自身はOpenGLコンテキストを管理するすべを持っていません。X Windowを使うlinux環境下でのコンテキスト管理はGLXが担っていました。(GLXはOpenGLとX Windowの間を取り持つものです)
以前はlinux(というよりunix)でOpenGLを使う時はX Windowが必ず絡むことになり、OpenGLとGLXを分けて使うことは無かったはずです。そのためライブラリとしてlibGL.soがOpenGLもGLXも含んでおり、リンク時には-lGLだけを指定すれば良かったのです。しかし時は流れ、今回のEGLを使う場合のようにOpenGLのライブラリとGLX(OpenGLコンテキスト管理)を分離したいという需要が出てきました。そこでGLVND (OpenGL Vendor Neutral Dispatch)という仕組みが産み出されて、libGL.so改めlibOpenGL.soがコンテキスト管理部分を含まないライブラリとして用意されるようになった…というものです。
このGLVNDの仕組み自体はドライバのバージョンが361.28から提供されていたようですが、バージョンが進むにつれ、390という最近のバージョンでは古い方法がサポートされなくなったということが推測されます。
最後に
きっかけはブログを読んでくれた方からの質問でしたが、一通り調査を終えて古いバージョンのドライバを使い続けるという後ろ向きな方法から脱却できたのは良かった…というのが正直な気持ちです。
ちなみにこのブログは日本語ですが、質問してくれた方はstackoverflowにも今回の現象を投稿してくれていたので、そちらにもそれっぽく回答しておきました。これでもし英語圏の方が同じ現象にハマっても解決策にたどり着けるようになると良いかなと思います。
カブクではOpenGLやCUDAなど、GPUを駆使した実装が得意だという方をお待ちしております。
その他の記事
Other Articles
関連職種
Recruit