MacのOpenGLのバージョン及びプロファイル

今日一日を費やしたので記録しておきます。

発端

今日は、PyOpenGLを使って、OpenGL 3.xでGLSL 1.4のモダンなOpenGLの勉強でもするぜー、と意気揚々としていたのですが、PyOpenGLで扱うGLSLのバージョンを1.2からどうやっても変えることが出来ず、ひたすら調べまくり、一日が終わったというわけでした。

OpenGLのバージョンの変更

言葉の使い方が悪いのでしょうか、「OpenGL バージョン変更」などでググると、新しいマシンを買いましょう、のような結果しか得られません。

ここではMacの話をしますが、Macには、内部的にOpenGLのバージョン(プロファイル)が用意されており、私のMac Book Air 13-inch, Mid 2011では、OpenGL 2.1、GLSL 1.2に設定されています*1OpenGL 3.xを使うには、これを変更しなければなりません。

ではどうやって変更するのか。正直これがサッパリわかりませんでした。

結論から言えば、NSなんちゃらなcocoaのメソッドにより変更できるようです。詳細は以下を参照。

OpenGL Programming Guide for Mac: Choosing Renderer and Buffer Attributes

ちなみに、WindowsではwglなんたらARBだかで、このプロファイルを変更できるようです。以下のサイトに詳しく書かれてそうな気がします。

Creating an OpenGL Context (WGL) - OpenGL.org

さらにちなみに、当然のことながらOSとハードウェアのサポートが無ければ、OpenGL 3.xは使えません。Macに関しては、サポート状態を以下のサイトで確認できます。

OS X 10.8.4 Core Profile OpenGL Info

私のMac Book Air 13-inch, Mid 2011は、OSがMountain Lionで、オンボのSandyBridgeのHD Graphics 3000がOpenGL 3.2とGLSL 1.5をサポートしているということが分かります。

もっと簡単にできないの?

GLUTは一部の拡張でプロファイルを指定できるようになってたりするという噂を聞いたり聞かなかったりするのですが、一番は、卒業してしまうことなのかもしれません。ティーポットが表示できなくなるのは寂しいですが…。詳細は床井先生の記事にて。

床井研究室 - (1) GLFW で OpenGL を使う

Pythonでの選択肢

PythonOpenGLをやろう!

となると当然のことながらbindingを利用することになるのですが、その代表格としてよく出てくるのが

の2つのライブラリです。

PyOpenGLは非常に素直なOpenGL, GLU, GLUTのラッパーで、C言語OpenGLを使う感覚で利用することが出来ます。OpenGL, GLU, GLUTという鉄板の構成は大変嬉しいのですが、残念ながら、Core Profileを変更することは出来ないようです。ウェブサイトにOpenGL 4.xまでサポートしてるぜーって書いてありますが、これは最初からプロファイルが新しい状態という前提なのでしょう。

一方、pygletはOpenGLとGLUのラッパーを提供していますが、GLUTはサポートしていません。その代わりに、独自の非常に高水準なウィンドウマネージャーなどの機能を提供しています。また、Core Profileの変更もサポートしているようです。

ということで、これまでPyOpenGLで色々やってきたのですが、現在、pygletに移行しています。ふぅ疲れた。

pygletは最新の1.2alphaでもOpenGL 3.xが使えないことが判明しました。原因はちょっと不明です。(1.2alphaではAGL脱却してCarbonになったってログに書いてあるから大丈夫な気がするんだけどなぁ)

Core Profile変更に対応したDrawing用のfork

Core Profileを変えれるようにした、1.2alphaのDarwin用のforkが公開されていました。

こっちはテスト的に実装してみました的な方(?)。

ちなみに、Core Profileに関するIssueは以下のページです。

そもそも、私の環境では、1.2alphaでウィンドウがが表示されないので、どうしようもないんですけどね〜。下のサイトでCocoaの完全な以降目指して頑張るよって書いてあるので気長に待つことにします。

だからといって、1.1だと、32bitでPythonを使う必要があるし*2、ウィンドウをマウスドラッグでリサイズ出来ないし*3、、、とPygletはMacだと少しバギーなのが難点です。

シェーダー in Pyglet1.1

とりあえず、素のOpenGL的な使い方をして、シャーダーが使えることは確認しました。ただし、これは明言されていることでも有りますが、PyOpenGLのような頑張っているラッパーとは違い、割りと素のままのラッパーなので、引数などをctypesでインタフェースを合わせる必要があります。ctypesは初めてなので、変なコトやっていう可能性は大ですが、とりあえず動いています。

少しだけコードの断片を載せておきます。変数の意味はお察し下さい。

  • Mac OS X 10.8.4 Mountain Lion
  • Python 2.7 (プリインストール)
  • Pyglet 1.1 stable
# Pythonのstrの変換
def to_c_str(self, pystr):
    return ctypes.cast(
        ctypes.pointer(ctypes.pointer(ctypes.create_string_buffer(pystr))),
        ctypes.POINTER(ctypes.POINTER(GLchar)))

# シェーダのソースコードの割り当て
glShaderSource(shader, 1, to_c_str(shader_src), None)

# シェーダーコンパイル結果の確認(プログラムリンク時もほぼ同様でOK)
c_result = ctypes.c_int()
glGetShaderiv(shader, GL_COMPILE_STATUS, c_result)
c_buf = ctypes.create_string_buffer(512)
glGetShaderInfoLog(shader, 512, None, c_buf)
if c_result.value == GL_FALSE:
    raise self.Error(c_buf.value)
elif c_buf.value:
    print c_buf.value

# glGenBuffers
buf = ctypes.pointer(ctypes.c_uint())
glGenBuffers(1, buf)
self.buffer = buf[0]

# valueは1次元np.array。
# ちなみに、OpenGLの行列は列順のため、numpyの行列は転置する必要がある。
# てことで、np.matrix.Tを使うか、transposeをGL_TRUEするか、どちらかが必要っぽそう。
location = glGetUniformLocation(self.program, name)
glUniformMatrix4fv(location, 1, transpose,
    value.ctypes.data_as(ctypes.POINTER(ctypes.c_float)))

完全にメモ帳状態になってますね。。。

本当だったら、別エントリでちゃんとまとめた方が良いのですが、とりまってことで。

pygletに関する情報の整理と追加情報

まず、pyglet 1.1は32bitモードで実行する必要があります。そのためには、$ defaults write com.apple.versioner.python Prefer-32-Bit -bool yesというコマンドを利用します。ところが、OpenCV2.4のコードが32bitモードだと動かないことが判明しました。

ということで、困ったわけですが、pygletは比較的頻繁にコミットされているようなので、タグ付けられているpyglet1.2alphaではなく、最新のコミットを利用することにしました。pipはリポジトリを直接指定することができるので、$ sudo pip install hg+https://code.google.com/p/pyglet/とすればインストールできます。

最新版のpygletを使ったところ、64bitモードで動作するし、Cocoaなウィンドウの表示(角丸)でリサイズも可能、と順風万端。のようですが、1.1に比べ動作が少しカクついているように感じました(タイマーのインターバルが安定していない?)。

もう一つ、"ApplePersistenceIgnoreState: Existing state will not be touched."という謎のエラーが発生しました。これは、OS Xの「再開」と呼ばれる機能らしいです(参考)。pygletのメーリスによると

Wow, thanks! With this information I found the ApplePersistenceIgnoreState key, and I've committed a fix that should prevent the org.python.python.savedState file from ever being created or used. This fixes the problem of new windows not being displayed after finishLaunching got called. It also explains why the problem was only showing up with certain builds of python and not pypy. There is an Xcode build setting that can be checked to prevent automatic state restoration for the application and I'm guessing the python from python.org doesn't have this set.

When you run a pyglet program from the terminal, there is a warning message saying that it won't be saving or restoring state. But I think I can live with that.

Google Groups

とありまして、ウィンドウ非表示になってしまった問題の原因でもあったようです。それと最後の段落によると、「まぁエラーメッセージ出るけど、別に問題無いと思うよ。」(超訳)ということなようなので、基本的には消すのではなく無視の方針らしいです。

*1:これは、glGetStringで確認できます。

*2:$ defaults write com.apple.versioner.python Prefer-32-Bit -bool yes

*3:https://groups.google.com/forum/#!topic/pyglet-users/u4d7QSFmS9s によると1.2alpha使ってね、って書いてあるようだが…