Entries

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

OpenGL ESとUIViewの比較

タグ: Objective-C iPhone OpenGL

同じ画像を50枚、画面内をくるくる跳ね回らせる処理を、OpenGL ESとUIViewでそれぞれ書いて実行してみた(SDK for iPhone OS 2.2)。

くるくるごちゃごちゃ

まずはOpenGL ES。
なお、以下の記述は全てSDK for iPhone OS 2.2のプロジェクトテンプレートを使ったものであり、それ以外のバージョンでも同じように書けるかどうかは不明である。

  1. プロジェクトテンプレート「OpenGL ES Application」を選んで新規プロジェクトを作成
  2. iPhone OS 2.1 Library(2.0でも良い/2.2には無い)のサンプルコード「CrashLanding」から「Texture2D.*」と「Ship.png」をコピーしてプロジェクトに加える
  3. EAGLViewに以下のインスタンス変数を追加
    Texture2D* texture;
    NSArray* array;
  4. 各画像(スプライト)の速度や位置を保持・計算するクラス・Fooを追加
    @interface Foo : NSObject {
    	CGPoint velocity;
    	NSInteger angularVelocity;
    	CGPoint coord;
    	NSInteger angle;
    }
    - (void)step;
    - (void)stepIn:(CGSize)size;
    @property CGPoint velocity;
    @property NSInteger angularVelocity;
    @property (readonly) CGPoint coord;
    @property (readonly) NSInteger angle;
    @end
    
    @implementation Foo
    @synthesize velocity, angularVelocity, coord, angle;
    - (id)init {
    	if(self = [super init]) {
    		velocity = CGPointMake(rand() % 11 - 5, rand() % 11 - 5);
    		angularVelocity = rand() % 21 - 10;
    	}
    	return self;
    }
    - (void)step {
    	NSInteger newAngle = angle + angularVelocity;
    	while(newAngle >= 360) newAngle -= 360;
    	while(newAngle < 0) newAngle += 360;
    	angle = newAngle;
    	coord = CGPointMake(coord.x + velocity.x, coord.y + velocity.y);
    }
    - (void)stepIn:(CGSize)size {
    	[self step];
    	if(fabs(coord.x) > size.width) velocity.x = -velocity.x;
    	if(fabs(coord.y) > size.height) velocity.y = -velocity.y;
    }
    @end
  5. EAGLView::initWithCorder: の後ろの方にテクスチャ関係の初期化コードを追加
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);
    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    
    texture = [[Texture2D alloc] initWithImage:[UIImage imageNamed:@"Ship.png"]];
    
    const NSUInteger objectCount = 50;
    NSMutableArray* mutableArray = [[NSMutableArray alloc] initWithCapacity:objectCount];
    for(int i = 0; i < objectCount; i++) {
    	Foo* foo = [[Foo alloc] init];
    	[mutableArray addObject:foo];
    	[foo release];
    }
    array = mutableArray;
  6. EAGLView::createFramebuffer の後ろの方に視点関係の初期化コードを追加
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrthof(-backingWidth / 2, backingWidth / 2, -backingHeight / 2, backingHeight / 2, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glViewport(0, 0, backingWidth, backingHeight);
    glScissor(0, 0, backingWidth, backingHeight);
  7. EAGLView::drawView を書き換える
    [EAGLContext setCurrentContext:context];
    
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    CGSize size = {
    	(backingWidth - texture.contentSize.width) / 2.0f,
    	(backingHeight - texture.contentSize.height) / 2.0f
    };
    
    for(Foo* foo in array) {
    	[foo stepIn:size];
    
    	glPushMatrix();
    	glTranslatef(foo.coord.x, foo.coord.y, 0.0f);
    	glRotatef(foo.angle, 0.0f, 0.0f, 1.0f);
    	[texture drawAtPoint:CGPointZero];
    	glPopMatrix();
    }
    
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
  8. EAGLView::dealloc に後始末コードを追加し、ビルドが通るように#importやらフレームワークやらを追加する

OpenGL ESを使ったのはこれが初めてなので、本当にこの書き方が効率いいのか分からなかったりするが、ともかく、これを第1世代iPod touchで実行した結果は24fps(Instrumentsで測定)だった。

続いてUIView。

  1. プロジェクトテンプレート「Window-Based Application」を選んで新規プロジェクトを作成
  2. サンプルコード「CrashLanding」から「Ship.png」をコピーしてプロジェクトに加える
  3. OpenGL ESの方で作成したクラスFooと、そのインスタンスとUIViewを関連づけるクラス・Barを追加
    @interface Bar : NSObject {
    	Foo* foo;
    	UIView* view;
    }
    @property (retain) Foo* foo;
    @property (retain) UIView* view;
    @end
    
    @implementation Bar
    @synthesize foo, view;
    - (void)dealloc {
    	[view release];
    	[foo release];
    	[super dealloc];
    }
    @end
  4. 新規ファイルテンプレート「Objective-C NSView subclas」でクラス・MyViewを追加し、以下のように書き換える
    @interface MyView : UIView {
        NSTimer *animationTimer;
        CGSize imageSize;
        NSArray* array;
    }
    @end
    
    @implementation MyView
    
    - (id)initWithFrame:(CGRect)frame {
        if (self = [super initWithFrame:frame]) {
    		self.backgroundColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
    		self.layer.sublayerTransform = CATransform3DMakeTranslation(CGRectGetMidX(frame), CGRectGetMidY(frame), 0.0f);
    		
    		UIImage* image = [UIImage imageNamed:@"Ship.png"];
    		imageSize = image.size;
    		
    		const NSUInteger objectCount = 50;
    		NSMutableArray* mutableArray = [[NSMutableArray alloc] initWithCapacity:objectCount];
    		for(int i = 0; i < objectCount; i++) {
    			NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    			
    			Bar* bar = [[[Bar alloc] init] autorelease];
    			bar.foo = [[[Foo alloc] init] autorelease];
    			
    			UIView* view = [[[UIImageView alloc] initWithImage:image] autorelease];
    			view.layer.anchorPoint = CGPointMake(0.5f, 0.5f);
    			view.center = CGPointZero;
    			[self addSubview:view];
    			bar.view = view;
    			
    			[mutableArray addObject:bar];
    			
    			[pool release];
    		}
    		array = mutableArray;
        }
        return self;
    }
    
    - (void)drawRect:(CGRect)rect {
        [animationTimer release];
    	animationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 target:self selector:@selector(drawView) userInfo:nil repeats:YES];
    }
    
    - (void)drawView {
    	CGSize size = {
    		(CGRectGetWidth(self.bounds) - imageSize.width) / 2.0f,
    		(CGRectGetHeight(self.bounds) - imageSize.height) / 2.0f
    	};
    	for(Bar* bar in array) {
    		[bar.foo stepIn:size];
    		bar.view.center = bar.foo.coord;
    		bar.view.transform = CGAffineTransformMakeRotation(bar.foo.angle * M_PI / 180);
    	}
    }
    
    - (void)dealloc {
        [array release];
        [image release];
        [animationTimer release];
        [super dealloc];
    }
  5. applicationDidFinishLaunching: でMyViewのインスタンスをウィンドウに追加
    - (void)applicationDidFinishLaunching:(UIApplication *)application {    
    	UIView* view = [[MyView alloc] initWithFrame:[window bounds]];
    	[window addSubview:view];
        [window makeKeyAndVisible];
    }
  6. ビルドが通るように#importやらフレームワークやらを追加する

このコードの実行結果は7fps
結構、遅い。

ところで、実行してみると分かるのだが、この2つのコードは挙動に決定的な違いがある。
それは、OpenGL ESを使ったコードが60fpsに満たない分は「処理落ち」しているのに対し、UIViewの方は「フレームスキップ」している、ということである(まあ処理落ちもしているが)。
実際、1秒間にdrawViewが呼ばれる回数を数えてみたところ、OpenGL ESの方がfpsと同じ24であったのに対し、UIViewの方は40回も呼ばれていた。
setAnimationDidStopSelector: を使って描画の完了を待てば、OpenGL ESと同じ挙動を示すようにもできるのだが、フレームレートはむしろ下がる。

スポンサーサイト

コメント

コメントの投稿

コメントの投稿
管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://idlysphere.blog66.fc2.com/tb.php/164-ab842adb
この記事にトラックバックする(FC2ブログユーザー)

Appendix

タグ

Blog内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。