コンピュータグラフィックスのレンダリングライブラリの一つ OpenGL はプラットフォームに依存しない仕様となっています。プラットフォームの一つに X11 があります。プラットフォームに依存する仕様は EGL (Embedded-System Graphics Library) にまとめられています。EGL は OpenGL とネイティブプラットフォームの間のインタフェースとして機能します。
OpenGL および EGL の実装の一つに Mesa 3D があります。その他に NVIDIA、AMD、Intel などの GPU ドライバの OpenGL/EGL 実装があります。
OpenGL を Python から利用するためのライブラリの一つに ModernGL があります。ここでは、OpenGL/EGL をライブラリを用いずに直接 Python から ctypes で利用する方法を記載します。実装としては Mesa をインストールすることにします。Debian9 の場合は以下のようにしてインストールできます。ウィンドウシステムとしては X11 を扱います。
sudo apt install libgl1-mesa-dev
sudo apt install libegl1-mesa-dev
/usr/lib/x86_64-linux-gnu/libGL.so
/usr/lib/x86_64-linux-gnu/libEGL.so
ドキュメント
ipython
ctypes で読み込む型はヘッダーファイルを利用して確認します。例えば egl.h によると EGLDisplay
は c_void_p
です。
typedef void *EGLDisplay;
from ctypes import c_int
from ctypes import c_uint
from ctypes import c_float
from ctypes import c_void_p
from ctypes import POINTER
from ctypes import CDLL
from ctypes import CFUNCTYPE
from ctypes import byref
from ctypes.util import find_library
from functools import partial
from numpy import zeros
from numpy import uint8
libEGL.so
の読み込みeglLib = CDLL(find_library('EGL'))
ctypes では利用する関数の引数と返り値の型を設定する必要があります。argtypes
と restype
を指定する方法は以下の方法以外にもあります。functools の partial()
で load()
の引数を一部固定できます。
def load(lib, name, restype, *args):
return (CFUNCTYPE(restype, *args))((name, lib))
loadEgl = partial(load, eglLib)
仕様によると eglGetDisplay
の引数の型は EGLNativeDisplayType
で返り値の型は EGLDisplay
です。EGL_DEFAULT_DISPLAY
を引数に指定して、DISPLAY
環境変数で指定されているものを利用するようにします。
EGLNativeDisplayType = c_void_p
EGLDisplay = c_void_p
EGL_DEFAULT_DISPLAY = EGLNativeDisplayType(0)
eglGetDisplay = loadEgl('eglGetDisplay', EGLDisplay, EGLNativeDisplayType)
display = eglGetDisplay(EGL_DEFAULT_DISPLAY)
if not display:
raise Exception('no EGL display')
EGLBoolean = c_uint
EGLint = c_int
eglInitialize = loadEgl('eglInitialize', EGLBoolean, EGLDisplay , POINTER(EGLint), POINTER(EGLint))
major = EGLint(0)
minor = EGLint(0)
if not eglInitialize(display, byref(major), byref(minor)):
raise Exception('cannot initialize EGL display')
初期化できない場合は適切な X11 を選択しているか確認します。例えば DISPLAY 0 の場合は以下のようになります。
DISPLAY=:0 ipython
初期化に成功している場合は EGL のバージョンが引数に指定した変数に格納されています。
In [1]: major
Out[1]: c_int(1)
In [2]: minor
Out[2]: c_int(4)
ただし、Mesa のバージョンと環境によっては以下のような警告が出ます。後に OpenGL で描画しても画像を取得できません。デフォルトのフレームバッファではなく、フレームバッファを新規に作成してから描画する必要があります。
libEGL warning: DRI2: failed to authenticate
更に OpenGL の API セットを選択します。既定値は EGL_OPENGL_ES_API
です。
EGL_OPENGL_API = 0x30A2
EGLenum = c_uint
eglBindAPI = loadEgl('eglBindAPI', EGLBoolean, EGLenum)
if not eglBindAPI(EGLenum(EGL_OPENGL_API)):
raise Exception('cannot bind API')
egl.h にある定数のうち必要なものを用意します。
EGL_BLUE_SIZE = 0x3022
EGL_GREEN_SIZE = 0x3023
EGL_RED_SIZE = 0x3024
EGL_DEPTH_SIZE = 0x3025
EGL_SURFACE_TYPE = 0x3033
EGL_NONE = 0x3038
EGL_PBUFFER_BIT = 0x01
EGLConfig = c_void_p
コンフィグ選択用の関数を読み込みます。
eglChooseConfig = loadEgl('eglChooseConfig', EGLBoolean, EGLDisplay, POINTER(EGLint), POINTER(EGLConfig), EGLint, POINTER(EGLint))
第二引数 POINTER(EGLint)
を生成するための補助関数を定義します。
def makeCtypeArray(ctype, contents):
arrayType = ctype * len(contents)
return arrayType(*contents)
ディスプレイの属性とその値を交互に配列に記載していきます。最後は EGL_NONE
で閉じます。
attribList = makeCtypeArray(EGLint, [
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_DEPTH_SIZE, 24,
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_NONE])
コンフィグを選択します。
config = EGLConfig()
configSize = EGLint(1)
numConfig = EGLint(0)
if not eglChooseConfig(display, attribList, byref(config), configSize, byref(numConfig)):
raise Exception('no suitable configs available on display')
Window 画面ではなくメモリ上の仮想的な空間に描画することをオフスクリーンレンダリングとよびます。OpenGL でオフスクリーンレンダリングを行うためには描画対象となるサーフィスを pBuffer (pixel buffer) で用意します。pBuffer ではなく Frame Buffer Object を利用することもできます。pBuffer サーフィスを作成するためには eglCreatePbufferSurface
を利用します。
EGL_HEIGHT = 0x3056
EGL_WIDTH = 0x3057
EGLSurface = c_void_p
eglCreatePbufferSurface = loadEgl('eglCreatePbufferSurface', EGLSurface, EGLDisplay, EGLConfig, POINTER(EGLint))
surfaceAttribList = makeCtypeArray(EGLint, [
EGL_WIDTH, 100,
EGL_HEIGHT, 100,
EGL_NONE])
surface = eglCreatePbufferSurface(display, config, surfaceAttribList)
if not surface:
raise Exception('cannot create pbuffer surface')
EGL_CONTEXT_CLIENT_VERSION = 0x3098
EGLContext = c_void_p
EGL_NO_CONTEXT = EGLContext(0)
eglCreateContext = loadEgl('eglCreateContext', EGLContext, EGLDisplay, EGLConfig, EGLContext, POINTER(EGLint))
contextAttribList = makeCtypeArray(EGLint, [EGL_NONE])
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribList)
if not context:
raise Exception('cannot create GL context')
一つのディスプレイに対して複数のサーフィスを作成できます。そのうちカレントとなるものを一つ選択します。OpenGL API を利用して描画する際の対象となります。
eglMakeCurrent = loadEgl('eglMakeCurrent', EGLBoolean, EGLDisplay, EGLSurface, EGLSurface, EGLContext)
if not eglMakeCurrent(display, surface, surface, context):
raise Exception('cannot make GL context current')
今回、ディスプレイのウィンドウと対応していない pBuffer をサーフィスとして利用しています。ModernGL を利用したときと同様に、pBuffer サーフィスにレンダリングした後に、サーフィスに描画されたデータを OpenGL の関数で読み出して画像として動作確認することにします。
libGL.so
の読み込みglLib = CDLL(find_library('GL'))
loadGl = partial(load, glLib)
glGetError を読み込みます。glcorearb.h を参照して必要な定数を定義します。
GL_NO_ERROR = 0
GLenum = c_uint
glGetError = loadGl('glGetError', GLenum)
glClearColor で緑色を指定します。
GLfloat = c_float
glClearColor = loadGl('glClearColor', None, GLfloat, GLfloat, GLfloat, GLfloat)
glClearColor(GLfloat(0.0), GLfloat(1.0), GLfloat(0.0), GLfloat(1.0))
if not glGetError() == GL_NO_ERROR:
raise Exception('glClearColor failed')
フレームバッファのカラーバッファを glClear で塗り潰します。
GL_COLOR_BUFFER_BIT = 0x00004000
GL_DEPTH_BUFFER_BIT = 0x00000100
GLbitfield = c_uint
glClear = loadGl('glClear', None, GLbitfield)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
if not glGetError() == GL_NO_ERROR:
raise Exception('glClear failed')