OpenGL ES の初期化

概要

iPhone で egl を利用する場合には、"OpenGL ES Appication" というテンプレートが用意されているのでこれを参照しながら、初期化処理を追っていけばよい.

テンプレートが何をしているのか、理解するのは重要なので、テンプレートを参照しながら、初期化周りを自分で実装してみることにする

layerClass の働き

egl を表示するための View オブジェクトを作成する必要があるが、 egl には専用の layer クラスであるCAEAGLLayer が用意されている

UIView 初期化時に、 CAEAGLLayer を利用して layer を作成するためには、ため、 UIView のクラスメソッド layerClass をオーバーライドする.

+ (Class) layerClass
{
    return [CAEAGLLayer class];
}

クラスメソッドなので、通常のインスタンスメソッドと異なり、 +(プラス) から定義されている点に注意すること.
クラスオブジェクト/クラスメソッドの概念は、C++には存在しない概念なので、十分な理解が必要になる.

軽く説明をしておくと、layer 作成時には、フレームワークが UIView から layerClass を経由して、layer を作成するための、ファクトリクラスオブジェクトを取得する.

フレームワークは、このファクトリクラスオブジェクトを利用して、layer インスタンスを作成することになる

予想では、フレームワーク中では以下のようなコードになっているはず...

/// 自信のViewオブジェクト
@interface MyView : UIView
@end 

@implementation MyView
/// ファクトリとしての ClassObject をフレームワークに渡すためにオーバーライド
+ (Class) layerClass
{
    return [CAEAGLLayer class];
}
@end

/// UIView の実装予想
@interface UIView
{
    CALayer* m_layer;
}
@property (nonatomic, retain) layer;
- (void) init;
@end

@implementation UIView

@synthesize layer = m_layer;

- (void) init
{
    Class a_LayerClass = [self layerClass];
    self.layer = [[[a_LayerClass class] alloc] init];
}
@end

egl コンテキストの作成

egl が描画で用いるためのコンテキストを作成しておく.

- (id)initWithCoder:(NSCoder*)coder
{
    if((self = [super initWithCoder:coder])){
        if( ![self setup] ){
            [self release];
            return nil;
        }
    }
    return self;
}

- (bool) setup
{
    // OES 
    CAEAGLLayer* a_layer;
    a_layer = (CAEAGLLayer*)self.layer;

    // レイヤのプロパティ設定
    a_layer.opaque = YES;  // 不透明レイヤ

    a_layer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                               kEAGLColorFormatRGBA8,                 // value
                                               kEAGLDrawablePropertyRetainedBacking,  // key
                                               kEAGLColorFormatRGBA8,                 // value
                                               kEAGLDrawablePropertyColorFormat,      // key
                                               nil];

    m_egl_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

    if( !m_egl_context || ![EAGLContext setCurrentContext:m_egl_context]){
        return false;
    }

    // ループ開始
    NSTimeInterval a_interval = 1.0 / 30.0f;
    self.loopTimer = [NSTimer scheduledTimerWithTimeInterval:a_interval target:self selector:@selector(onTick) userInfo:nil repeats:YES];

    return true;
}

layer のプロパティである、drawableProperties は、必要に応じて設定すればよい

わたしは、このプロパティ設定で exception が出てしまっていて大はまりしていたのだが、原因は前述の layerClass クラスメソッドが、インスタンスメソッドとして定義されていたのが原因だった.

NSDictionaly は、 dictionaryWithObjectsAndKeys 指定の初期化では、 value, key の順でペアを指定する.

テンプレートでは、以下のように指定している.

eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                    [NSNumber numberWithBool:NO],
                                    kEAGLDrawablePropertyRetainedBacking,
                                    kEAGLColorFormatRGBA8,
                                    kEAGLDrawablePropertyColorFormat, nil];

この例では、第一項目に "NO" 第二項目に、kEAGLDrawablePropertyRetainedBacking を指定しているので、"kEAGLDrawablePropertyRetainedBacking=NO"という設定を行っていることになる.

※ 2008.12.02 tentoo さんのご指摘により、NSDictionary の設定を修正

フレームバッファの作成

egl の話なので、それほど難しい部分は無いはず...

- (bool)createFramebuffer {
    glGenFramebuffersOES( 1, &m_view_framebuffer );
    glGenRenderbuffersOES( 1, &m_view_renderbuffer );
    
    glBindFramebufferOES( GL_FRAMEBUFFER_OES, m_view_framebuffer );
    glBindRenderbufferOES( GL_RENDERBUFFER_OES, m_view_renderbuffer );
    [m_egl_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
    glFramebufferRenderbufferOES( GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, m_view_renderbuffer );

    glGetRenderbufferParameterivOES( GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &m_backing_width );
    glGetRenderbufferParameterivOES( GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &m_backing_height );

    // depth
    glGenRenderbuffersOES( 1, &m_depth_buffer );
    glBindRenderbufferOES( GL_RENDERBUFFER_OES, m_depth_buffer );
    glRenderbufferStorageOES( GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, m_backing_width, m_backing_height );
    glFramebufferRenderbufferOES( GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, m_depth_buffer );
    
    return true;
}

egl 関連のドキュメントは、

http://www.khronos.org/opengles/sdk/1.1/docs/man/
http://www.khronos.org/opengles/1_X/
http://www.imgtec.com/powervr/insider/sdk/KhronosOpenGLES1xMBX.asp

このあたりから読めるようだが、フレームバッファ作成時に出てくる gl*OES という関数のドキュメントはどこを探せばよいのか不明.
(教えて偉い人)

なんとなく、やっていることぐらいならわかるから、良しとする

描画

こちらは、egl の命令が並ぶだけなので、注意点は特になし

IB での View の割り当てについて

テンプレートを参照しながら、初期化周りを書いていくと、テンプレートのリソースファイルに View 用の xib ファイルが無いことに気がつく.

冷静に見てみると、 MainWindow の全体に View オブジェクトが貼り付けられているので、適当な IBOutlet に接続すれば良い.

テンプレートと同じ構成にしたければ、 MainView 全体を覆うような形で View オブジェクトを配置し、 Class Identity に作成した View クラスを関連づけておけばよい.

当然、View だけ別の xib ファイルにしておいても良い.

フレームワークの追加

OpenGLES.framework と Quartz.framework を利用するので、追加しておくこと.

利用するフレームワークは、デフォルトでインストールしたときには

/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.1.sdk/System/Library/Frameworks/OpenGLES.framework
/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.1.sdk/System/Library/Frameworks/QuartzCore.framework

にインストールされている.

※ テンプレートに追加されている、フレームワークを選択して、"情報を見る"からパスが確認できる

おまけ

Finder で、パスを指定して移動するには、メニューバーから[移動]-[フォルダへ移動](もしくは、shift + ctrl + G)で行える.

...逆に、ファイルからパスを簡単にコピーしてくる方法を知りたい
([情報を見る]から、コピーのめんどい)