モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

ライブラリを用いない OpenGL/EGL の使い方 (X11、Python)

モーダルを閉じる

ステッカーを選択してください

お支払い手続きへ
モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2019/04/17
最終更新最終更新
2023/11/17
記事区分記事区分
一般公開

目次

    低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。

    コンピュータグラフィックスのレンダリングライブラリの一つ 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
    

    ドキュメント

    ディスプレイ取得、初期化、コンフィグ選択、サーフィス作成、コンテキスト作成、カレント化 (EGL)

    ipython
    

    必要なライブラリの読み込み

    ctypes で読み込む型はヘッダーファイルを利用して確認します。例えば egl.h によると EGLDisplayc_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 では利用する関数の引数と返り値の型を設定する必要があります。argtypesrestype を指定する方法は以下の方法以外にもあります。functoolspartial()load() の引数を一部固定できます。

    def load(lib, name, restype, *args):
        return (CFUNCTYPE(restype, *args))((name, lib))
    
    loadEgl = partial(load, eglLib)
    

    ディスプレイの取得 eglGetDisplay

    仕様によると 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')
    

    ディスプレイの初期化 eglInitialize

    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
    

    API のバインド eglBindAPI

    更に 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')
    

    コンフィグの選択 eglChooseConfig

    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')
    

    サーフィスの作成 eglCreatePbufferSurface

    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')
    

    コンテキストの作成 eglCreateContext

    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')
    

    作成したコンテキストとサーフィスのカレント化 eglMakeCurrent

    一つのディスプレイに対して複数のサーフィスを作成できます。そのうちカレントとなるものを一つ選択します。OpenGL API を利用して描画する際の対象となります。

    eglMakeCurrent = loadEgl('eglMakeCurrent', EGLBoolean, EGLDisplay, EGLSurface, EGLSurface, EGLContext)
    
    if not eglMakeCurrent(display, surface, surface, context):
        raise Exception('cannot make GL context current')
    

    描画 (OpenGL)

    今回、ディスプレイのウィンドウと対応していない 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')
    

    サーフィスからのデータの読み出し

    OpenGL には複数のバッファが登場します。描画する対象となるバッファをフレームバッファとよびます。フレームバッファはカラーバッファ、デプスバッファ、ステンシルバッファから構成されています。

    フレームバッファには複数のカラーバッファが存在します。描画する際は対象となるカラーバッファを選択する必要があります。フレームバッファに存在しないカラーバッファを選択するとエラーになります。glReadBuffer でカラーバッファから情報を読み出す場合も同様です。

    フレームバッファには OpenGL コンテキストを作成した際にデフォルトで用意されているものglGenFramebuffers で作成したものがあります。ここでは新規にフレームバッファを作成していないため、描画されたフレームバッファはデフォルトのものということになります。

    pBuffer を利用してオフスクリーンレンダリングを行っている際は不要ですが、オンスクリーンレンダリングで実際の画面に表示する場合は画面のちらつきを抑えることが求められます。例えば FRONT と BACK に複数のカラーバッファを用意して描画は BACK に対して行い eglSwapBuffers で FRONT に内容をコピーすることでちらつきを抑えることができます。これをダブルバッファリングとよびます。

    GL_FRONT = 0x0404
    GL_BACK = 0x0405
    glReadBuffer = loadGl('glReadBuffer', None, GLenum)
    glReadBuffer(GL_BACK)
    if not glGetError() == GL_NO_ERROR:
        raise Exception('glReadBuffer failed')
    

    NumPy で用意した配列に、カラーバッファの内容を glReadPixels で読み出します。

    GLint = c_int
    GLsizei = c_int
    GLvoid_p = c_void_p
    GL_RGB = 0x1907
    GL_UNSIGNED_BYTE = 0x1401
    
    glReadPixels = loadGl('glReadPixels', None, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid_p)
    
    x,y = 0,0
    width,height = 100,100
    format = GL_RGB
    type = GL_UNSIGNED_BYTE
    image = zeros((height, width, 3), dtype=uint8)
    
    glReadPixels(GLint(x), GLint(y), GLsizei(width), GLsizei(height), format, type, image.ctypes.data_as(GLvoid_p))
    if not glGetError() == GL_NO_ERROR:
        raise Exception('glReadPixels failed')
    

    成功すると以下のような画像が得られます。OpenCV の関数でファイルに保存できます。

    import cv2 as cv
    cv.imwrite('green.png', image)
    

    Matplotlib でも確認できます。

    import matplotlib.pyplot as plt
    plt.imshow(image)
    plt.show()
    

    リソース解放 (EGL) eglDestroySurfaceeglDestroyContexteglTerminate

    EGL_NO_SURFACE = EGLSurface(0)
    
    eglDestroySurface = loadEgl('eglDestroySurface', EGLBoolean, EGLDisplay, EGLSurface)
    eglDestroyContext = loadEgl('eglDestroyContext', EGLBoolean, EGLDisplay, EGLContext)
    eglTerminate = loadEgl('eglTerminate', EGLBoolean, EGLDisplay)
    
    # カレントの終了
    if not eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT):
        print('eglMakeCurrent failed')
    
    # サーフィスの破棄
    if not eglDestroySurface(display, surface):
        print('eglDestroySurface failed')
    
    # コンテキストの破棄
    if not eglDestroyContext(display, context):
        print('eglDestroyContext failed')
    
    # ディスプレイの解放
    if not eglTerminate(display):
        print('eglTerminate failed')
    
    Likeボタン(off)0
    詳細設定を開く/閉じる
    アカウント プロフィール画像

    低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    >>さらに詳しくステッカーを贈る
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      ログインする

      関連記事