今はこれが精一杯-1

私・・・もっとGLくんのこと、わかってあげたいの


前にこんな日記つけました。

下手なクラスを作ったために、処理効率が悪くなった話でした。

そこで、今回はあの時の失敗を乗り越えるべく、新しく、テクスチャで描画するオブジェクト(座標などの情報を持つ)の一般的なクラスを作成してみることにしました。

とりあえず今回は、

  • 2Dのみ考慮する
  • 今の時点では、VBO、頂点インデックス、マルチテクスチャ等による最適化は考慮しない
  • 1つのテクスチャで多数のオブジェクトを描画する場合の処理をなるべく効率化する ←ここが今回の主な目標
  • iOS SDKの機能と直接やり取りする部分以外は、なるべくC,C++で実装する
  • それ以外の点については、なるべく作る人(主に私)にとって負担が少ないようにする

という方針でやってみます。
C++なんて最近触っていないので、ちゃんと書けるか不安ですが・・・

ちなみに、最近気づいたのですが、C, C++で実装するということは、速度面以外にも利点があります。
iPhoneAndroid両方に対応するアプリを作る際に、コードを共有することができる可能性があるということです。
もちろんその場合、AndroidはNDKを使わないといけないし、画面サイズの違いも考慮しなければならないので、簡単にはいかないかもしれませんが。

それでは、順にコード書いて行きます。

テクスチャの読み込み

これに関しては、iOSのUIImageから読み込む必要があるので、Objective-Cクラスにします。
テクスチャのロード、アンロードは、それほど頻繁に行うことでもありませんし、速度はそれほど重要ではないでしょう。

// テクスチャ管理クラス
// 生のテクスチャIDとLoadTexture関数をラップする。
// テクスチャ描画はTextureDrawerクラスで行う。
// このクラスはテクスチャに関連付けられたTextureDrawerオブジェクトの生成を行う。
@interface Texture : NSObject
{
    NSString* _textureName;
    GLuint _texture;
    BOOL _loaded;
}

@property(assign, nonatomic, readonly) BOOL isLoaded;
@property(assign, nonatomic, readonly) GLuint raw;

-(id)initWithImageName:(NSString*)textureName;
-(id)load;
-(void)unload;
-(TextureDrawer*)drawer;
@end

@implementation Texture

@synthesize isLoaded = _loaded;
@synthesize raw = _texture;

-(id)initWithImageName:(NSString*)textureName {
    self = [super init];
	
    if (self) {
        _textureName = textureName;
        _texture = 0;
        _loaded = NO;
    }
	
    return self;
}

-(void)dealloc {
    if (_texture) {
        glDeleteTextures(1, &_texture);
        _texture = 0;
    }
	
    [super dealloc];
}

-(id)load {
    [self unload];
	
    _texture = LoadTexture(_textureName);
	
    if (_texture) {
        _loaded = YES;
    }
	
    return self;
}

-(void)unload {
    if (_loaded) {
        glDeleteTextures(1, &_texture);
        _loaded = NO;
    }
}

-(TextureDrawer*)drawer {
    if (!_loaded) {
        return nil;
    }
	
    return [[[TextureDrawer alloc] initWithTexture:_texture] autorelease];
}

@end

GLuint LoadTexture(CGImageRef image) {
    GLuint texture;
	
    // 画像の大きさを取得する
    size_t width = CGImageGetWidth(image);
    size_t height = CGImageGetHeight(image);
	
    // ビットマップデータを用意する
    GLubyte* imageData = (GLubyte*)malloc(width * height * 4/* rgba */);
    memset(imageData, 0, width * height * 4);
	
    if (!imageData) {
        NSLog(@"Error:failed to memory allocation.");
        return 0;
    }
	
    CGContextRef imageContext = CGBitmapContextCreate(imageData, width, height, 8, width * 4,
					  CGImageGetColorSpace(image),
					  kCGImageAlphaPremultipliedLast);
	
    CGContextDrawImage(imageContext, CGRectMake(0, 0, (CGFloat)width, (CGFloat)height), image);
    CGContextRelease(imageContext);
	
    // OpenGL用のテクスチャを生成
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
	
    free(imageData);
    return texture;
}

rawプロパティによって、OpenGLの生のテクスチャハンドルを取得できるようにしてあります。
それから、今回の主旨からすると、本来不要なものなのですが、ちょっとしたときに、
「速度は度外視して、なるべくObjective-Cで書きたい」
といったようなときに使う用のTextureDrawerという(非効率的な)描画専門のクラスを作成しました。

@interface TextureDrawer : NSObject
{
    GLuint _texture;
    float _x, _y, _width, _height, _u, _v, _tw, _th;
    int _r, _g, _b, _a;
}

@property(assign, nonatomic, readwrite) float x;
@property(assign, nonatomic, readwrite) float y;
@property(assign, nonatomic, readwrite) float width;
@property(assign, nonatomic, readwrite) float height;
@property(assign, nonatomic, readwrite) float u;
@property(assign, nonatomic, readwrite) float v;
@property(assign, nonatomic, readwrite) float tw;
@property(assign, nonatomic, readwrite) float th;
@property(assign, nonatomic, readwrite) int r;
@property(assign, nonatomic, readwrite) int g;
@property(assign, nonatomic, readwrite) int b;
@property(assign, nonatomic, readwrite) int a;

-(id)initWithTexture:(GLuint)texture;
-(void)draw;
-(void)reset;
@end

@implementation TextureDrawer

@synthesize x = _x;
@synthesize y = _y;
@synthesize width = _width;
@synthesize height = _height;
@synthesize u = _u;
@synthesize v = _v;
@synthesize tw = _tw;
@synthesize th = _th;
@synthesize r = _r;
@synthesize g = _g;
@synthesize b = _b;
@synthesize a = _a;

-(id)initWithTexture:(GLuint)texture {
    self = [super init];
	
    if (self) {
        _texture = texture;
       [self reset];
    }
	
    return self;
}

-(void)draw {
    DrawTexture(_texture, _x, _y, _width, _height, _u, _v, _tw, _th, _r, _g, _b, _a);
}

-(void)reset {
    _x = _y = _u = _v = 0.0f;
    _width = _height = _tw = _th = 1.0f;
    _r = _g = _b = _a = 255;
}
@end

void DrawTexture(GLuint texture, float x, float y, float width, float height,
		 float u, float v, float tw, float th, int r, int g, int b, int a) {
    GLfloat vertices[] = {
        x - width / 2, y - height / 2,
        x + width / 2, y - height / 2,
        x - width / 2, y + height / 2,
        x + width / 2, y + height / 2,
    };
	
    // マッピング座標の格納順は、左下、右下、左上、右上の順
    GLfloat texCoords[] = {
        u,      v + th,
        u + tw, v + th,
        u,      v,
        u + tw, v,
    };
	
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, texture);
    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glEnableClientState(GL_VERTEX_ARRAY);
    glColor4ub(r, g, b, a);
    glDisableClientState(GL_COLOR_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisable(GL_TEXTURE_2D);
}


このTextureDrawerの利点は、テクスチャに対して描画のみしか行えない安全なハンドルとしての役割を持つところです。
どこかのクラスが、引数でこのオブジェクトを受け取ったとしても、描画すること以外はできません。
コードを疎結合にする場合には、こういったアプローチが有効な場合もあります。
欠点は、汎用的に高速な描画を行おうとした場合には、使い物にならないという点です。

といったわけで、ここから先は、TextureDrawerは全く登場しません。

主に自分メモ用なんですが、コードを貼付けているので、どうしても長くなってしまいます。
分割して次へ続けます。


続く