連載
» 2010年01月08日 00時00分 公開

はじめよう3D描画、BREWでOpenGL ESプログラミング“BREW”アプリケーション開発入門(6)(2/3 ページ)

[末永貴一(エイチアイ),@IT MONOist]

プログラミングの前に

 早速、プログラミングを行いたいところですが、その前にOpenGL ESにおける2つの前提知識を紹介します。

 それは、(1)OpenGL ESは「ステートマシン」であるということ、(2)環境非依存化のための「EGLインターフェイス」が存在するということです。

 いずれもOpenGLと同様の発想なので、OpenGLをご存じの方はご理解いただけると思います。それでは、この2つについて説明します。

ステートマシン

 ステートマシンとは、ステート(状態)の名前が示すとおり、“状態”を持つマシン(機械)です。一般的に、決められた状態を一定の条件で順番に遷移していく機械のことを指します。

 これは、状態遷移図などで表現され、プログラミングのスタイルにも大きく影響します。例えば、値の取得をする場合、オブジェクト指向ではオブジェクトから値の取得を行います。これに対して、ステートマシンの実装では、状態から値を取得するため、“現在の状態が何か”が重要になります。多少煩雑な感じはしますが、構造的にシンプルなので、慣れてしまえば特に問題ないと思います。

EGLインターフェイス

 OpenGL ESは、環境非依存の標準APIセットであるため、その中に環境依存のAPIは含まれていません。

 しかし、実際には描画ターゲット指定などの環境依存部分があります。このような環境依存部分を切り出したものがEGLインターフェイスです(OpenGLでは、Windows用の「WGL」やUNIXのX Window System用の「GLX」がこれに相当します)。OpenGL ESのプログラミングでは、別途、EGLインターフェイスの記述が必要になり、この2つのセットではじめて3D描画が可能になるということです。

 EGLは、OpenGL ESとは別の仕様として定義されており、OpenGL ESとは別にバージョンのナンバリングがされています。なお、現在のEGLのバージョンは「1.2」ですが、どのバージョンのEGLがサポートされているかは環境に依存しますので注意してください。

EGLとOpenGL ES 図3 EGLとOpenGL ES

OpenGL ESプログラミングの基礎

 以上の前提知識は、そのままOpenGL ESプログラム構造の大枠にもかかわります。

 大別すると、EGLインターフェイスの設定部分と、OpenGL ESプログラミング自体の部分の2つに分けることができます。もちろん、BREW環境でのプログラミングですので、BREW自体の処理は全体のベースにあります。

※注2:以降の解説では、前回同様、新規にプロジェクトを作成しています。また、紹介しているコード内では、エラー処理を適宜割愛している部分があります。実際には、エラー処理を記述する必要がありますので注意してください。


 それでは、OpenGL ESプログラミングをBREW環境上で行うために必要な処理を確認します。

 これまで解説してきたとおり、BREW上で機能を利用するためには、各機能のインスタンスを生成する必要があります。OpenGL ESの場合も同様です。

boolean setup(oes3d* pMe){
      ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_GL, (void **)&pMe->m_pIGL);
      ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_EGL, (void **)&pMe->m_pIEGL);
      return TRUE;
} 

 「ISHELL_CreateInstance()」に、「AEECLSID_GL」と「AEECLSID_EGL」を指定して、OpenGL ES、EGLのインスタンス生成を行います。2つのインスタンスは、関数をまたいで使用するため、Applet構造体にメンバを追加しておきます。

IGL*            m_pIGL;                 //IGL インスタンス
IEGL*           m_pIEGL;                //IEGL インスタンス 

 併せて、ヘッダーのインクルードと関数宣言をします。

#include "AEEGL.h"
 
boolean setup(oes3d* pMe); 

 なお、BREW環境ではOpenGL ES、EGLともに接頭辞として「IGL_」「IEGL_」が各関数に付き、その第1引数に「IGL」「IEGL」インスタンスを渡す必要があります。そのため、OpenGL ES、EGLともに関数の引数が1つ多くなっています。

※注3:これを回避して、標準のOpenGL ESとEGLの関数を使う方法については、後述します(本文末で紹介)。


EGLインターフェイスのセットアップ

 次に、環境依存部のEGLインターフェイスの設定を行います。EGLインターフェイスのセットアップは、大きく以下の流れで行います。

  1. 描画対象のネイティブディスプレイの取得 
    ・IEGL_eglGetDisplay(IEGL, Display)
  2. EGLの初期化 
    ・IEGL_eglInitialize (IEGL, Display, egl_major, egl_minor)
  3. EGLコフィグレーションの選択 
    ・IEGL_eglGetConfigs (IEGL, Display, &config, size, &numcfg)
  4. EGLウィンドウサーフェイスの生成 
    ・IEGL_eglCreateWindowSurface (IEGL, Display, config, win, attrib_list)
  5. EGLグラフィックスコンテキストの作成 
    ・IEGL_eglCreateContext (IEGL, Display, config, share_list, attrib_list)
  6. EGLグラフィックスコンテキストのカレント設定 
    ・IEGL_eglMakeCurrent (IEGL, Display, draw, read, context)

 以上の手順に従って、BREW環境でのEGLセットアップを記述すると以下のようになります。

boolean setupEGL(oes3d* pMe){
        IBitmap*  m_pDDBitmap;                  // デバイス (画面) ビットマップ
        EGLConfig myConfig;                     // EGLフレームバッファ構成の格納
        EGLint ncfg;                            // 結果コンフィグ数の格納
        NativePixmapType pDIB = NULL;           // DIBの格納(IDIB*のtypedef)
        pMe->m_eglDisplay = EGL_NO_DISPLAY;     // EGLディスプレイの初期化
        pMe->m_eglSurface = EGL_NO_SURFACE;     // EGLサーフェイスの初期化
        pMe->m_eglContext = EGL_NO_CONTEXT;     // EGLコンテキストの初期化
 
        // 1. 描画対象ネイティブディスプレイの取得
        pMe->m_eglDisplay = IEGL_eglGetDisplay(pMe->m_pIEGL, pMe->a.m_pIDisplay);
        if (pMe->m_eglDisplay == EGL_NO_DISPLAY || 
            IEGL_eglGetError (pMe->m_pIEGL) != EGL_SUCCESS){
            return FALSE;
        }
        // 2. EGLの初期化
        if (IEGL_eglInitialize (pMe->m_pIEGL, pMe->m_eglDisplay, NULL, NULL) == EGL_FALSE ||
            IEGL_eglGetError (pMe->m_pIEGL) != EGL_SUCCESS){
            return FALSE;
        }
        // 3. EGLコンフィグレーションの選択
        IEGL_eglGetConfigs (pMe->m_pIEGL, pMe->m_eglDisplay, &myConfig, 1, &ncfg);
        // 4. EGLウィンドウサーフェイスの生成
        IDISPLAY_GetDeviceBitmap (pMe->a.m_pIDisplay, &m_pDDBitmap);  //デバイスBMP取得
        IBITMAP_QueryInterface (m_pDDBitmap, AEECLSID_DIB, (void**)&pDIB); // IDIB作成
        pMe->m_eglSurface = IEGL_eglCreateWindowSurface (pMe->m_pIEGL, 
                                                         pMe->m_eglDisplay,
                                                         myConfig, pDIB, NULL);
        IDIB_Release (pDIB);
        IBITMAP_Release (m_pDDBitmap);
        if (pMe->m_eglSurface == EGL_NO_SURFACE ||
            IEGL_eglGetError(pMe->m_pIEGL) != EGL_SUCCESS){
            return FALSE;
        }
        // 5. EGLグラフィックスコンテキストの作成
        pMe->m_eglContext = IEGL_eglCreateContext (pMe->m_pIEGL,
                                                   pMe->m_eglDisplay, myConfig, 0, 0);
        if (pMe->m_eglContext == EGL_NO_CONTEXT ||
            IEGL_eglGetError (pMe->m_pIEGL) != EGL_SUCCESS){
            return FALSE;
        }
        // 6. EGLグラフィックスコンテキストのカレント設定
        if (IEGL_eglMakeCurrent (pMe->m_pIEGL, pMe->m_eglDisplay, pMe->m_eglSurface,
                                 pMe->m_eglSurface, pMe->m_eglContext) == 
            EGL_FALSE || IEGL_eglGetError (pMe->m_pIEGL) != EGL_SUCCESS){
            return FALSE;
        }
        return TRUE;
} 
ソース1 EGLインターフェイスのセットアップ

 前述したsetup()に、setupEGL()を加えて初期化処理を行えるようにします。

boolean setup(oes3d* pMe){
      ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_GL, (void **)&pMe->m_pIGL);
      ISHELL_CreateInstance(pMe->a.m_pIShell, AEECLSID_EGL, (void **)&pMe->m_pIEGL);
      if(setupEGL(pMe) == FALSE){
            return FALSE;
      }
      return TRUE;
} 

 「EGLDisplay」「EGLSurface」「EGLContext」インスタンスは関数をまたいで利用するため、Applet構造体のメンバに追加し、併せて、関数宣言も追加します。

EGLDisplay      m_eglDisplay;   //EGL ディスプレイコネクション
EGLSurface      m_eglSurface;   //EGL ウィンドウサーフェイスのハンドル
EGLContext      m_eglContext;   //EGL レンダリングコンテキストのハンドル 
boolean setupEGL(oes3d* pMe); 

 以上のように、最初の描画ターゲットの取得を「IDisplay」インターフェイスから行う部分からはじまり、基本的には標準のEGLの初期化フローにのっとっていることが分かります。

 特に、「EGLウィンドウサーフェイス」の生成部分では、BREW環境特有の部分があり、少し注意する必要があります。

 EGLのサーフェイスとは、描画対象面のことを表し、代表的なものに「ウィンドウサーフェイス」と「ピックスマップサーフェイス」があります。“ウィンドウ”という名前が、若干誤解を招きやすいのですが、基本的には実行環境の標準描画ターゲットと同義だと思っていただいて問題ありません。通常、EGLサーフェイスを作成する場合はウィンドウサーフェイスを利用します。

 一方、ピックスマップサーフェイスですが、「オフスクリーンサーフェイス」と呼ばれる描画結果をオフスクリーンで処理するためのEGLサーフェイスです。描画結果をエフェクトやテクスチャとして利用――つまり、処理可能なメモリ上のデータとして扱うことが――できます。オフスクリーンサーフェイスには、ほかにも「ピクセルバッファサーフェイス」というEGLサーフェイスもありますが、ピックスマップサーフェイスは、ユーザーが確保した領域にレンダリングできるのに対して、ピクセルバッファは、EGL実装により確保されるという違いがあります。

 BREWでウィンドウサーフェイスを作成する際、ダブルバッファの実装を考慮する必要があります。

 通常のBREWアプリケーションでは、ダブルバッファは内部的に提供されているため、自身で実装する必要はありません。しかし、EGLからはBREWのダブルバッファを利用できないため、メインの描画ターゲットであるIDisplayからバッファ領域を作成する必要があります。

 それが以下の部分です。

IDISPLAY_GetDeviceBitmap (pMe->a.m_pIDisplay, &m_pDDBitmap);         //デバイスBMP取得
IBITMAP_QueryInterface (m_pDDBitmap, AEECLSID_DIB, (void**)&pDIB);   // IDIB作成
pMe->m_eglSurface = IEGL_eglCreateWindowSurface (pMe->m_pIEGL, pMe->m_eglDisplay,
                                                 myConfig, pDIB, NULL); 

 「IDISPLAY_GetDeviceBitmap()」により、デバイスビットマップを取得して、バッファ領域を作成します。しかし、このままだと環境依存の「DDB(Device Dependent Bitmap)」であるため、EGLから利用できないので、環境非依存の「DIB(Device Independent Bitmap)」に変換します。

 DIB変換には、「IBITMAP_QueryInterface()」を利用します。以上により、ウィンドウサーフェイスがダブルバッファ可能な状態になります。

 また、EGLのエラー処理として、「IEGL_eglGetError()」を使っていますが、EGLは、関数失敗時にエラーを返さずにエラー状態になります。そのため、エラーチェックは現在のエラー状態を確認するという形で行うため、「IEGL_eglGetError()」を使ってエラー状態を確認し、「EGL_SUCCESS」という状態でなければ、何らかのエラー状態になっていると判断できます。

Copyright © ITmedia, Inc. All Rights Reserved.