サイトトップ

Director Flash 書籍 業務内容 プロフィール
Performance Optimization of Starling framework
Stage3D

Starlingフレームワークの最適化

Starling Wiki「Performance Optimization」(パフォーマンスの最適化)を題材として、おもな内容の紹介と考察を加える。


01 状態の変更をできるだけ少なく

画面に描かれる表示オブジェクト(DisplayObject)を代表するのがQuadオブジェクト。テクスチャを表示するImageオブジェクトもQuadクラスを継承する。

01-01 状態を決めるプロパティ

Starlingは多くのQuadオブジェクトを、できるだけまとめてGPUに送ろうとする。ところが、異なる「状態」(state)のQuadオブジェクトがあると、「状態の変更」(state change)が起こる。すると、それまでにまとめたQuadオブジェクトが送られて描画されてしまう。

01-02 Quadオブジェクトの着色

モバイルデバイスでは、テクスチャに「着色」すると描画が滑らかでなくなることがある。

着色のあるなしを頻繁に変えるより、親オブジェクトに1未満(たとえば0.999)のアルファを設定する。

01-03 Starling.showStatsプロパティでフレーム当たりの描画数を確かめる

Starling.showStatsプロパティtrueにすると、コンテンツの動作情報が示される。最新のバージョンではフレーム当たりの描画回数が確かめられるので、その数をできるだけ少なく抑える。


02 表示オブジェクトの描画を最適化する

02-01 画家のアルゴリズム

StarlingフレームワークはFlashと同じく、背面の表示オブジェクトから順に手前のオブジェクトを塗り重ねる(図001)。これを「画家のアルゴリズム」という。

図001■遠景から順に近景を塗り重ねていく

最背面から重ね順にしたがって描かれる表示オブジェクトは、状態が同じであればGPUでまとめて描画される。表示リストをつくるときも、描画の順序を考えなければならない(図002)。

図002■表示オブジェクトを重ね順に応じて3つのSpriteインスタンスにまとめる

02-02 テクスチャアトラス

表示オブジェクトのテクスチャが異なると、状態は違うことになる。しかし、同じテクスチャアトラスからロードすれば状態は同じと扱われ、Starlingフレームワークは一度に描画できる(図003)。

図003■表示オブジェクトのテクスチャをひとつのアトラスからロードすれば一度に描画できる

テクスチャアトラスは、スプライトシートからつくる(図004)。手順とスクリプティングについては、ADC OnAir「Stage3Dを学ぼう!Flashで2Dコンテンツ」(録画01:17:30-01:33:30)、またはAdobe Developer Connection「Starlingフレームワークのスプライトシートを使ったアニメーション」参照。

図004■[ライブラリ]のシンボルを右クリックして[スプライトシートを生成]
図004

アトラスの大きさは、2048×2048ピクセル以内(一部のモバイルデバイスにおけるテクスチャの最大値)。テクスチャアトラスを分けるときは、表示オブジェクトの重ね順でまとめる(図005)。

図005■テクスチャアトラスは表示オブジェクトの重ね順でまとめる
図005上
図005↓
図005下

03 Spriteオブジェクトのフラット化とQuadBatchクラス

Spriteインスタンスに加えた子オブジェクトの見た目がしばらく変わらないときは、Sprite.flatten()メソッドを呼出すと描画が最適化される。その描画を速めるために、おもに内部的に用いられるのがQuadBatchクラスだ。

03-01 Spriteオブジェクトのフラット化

Sprite.flatten()メソッドを呼出しすと、Starlingは子インスタンスを予め処理して、データをGPUにアップロードする。すると、以降のフレームでは、そのデータがそのまま描かれ、CPUは使われない。

03-02 QuadBatchクラス

Spriteオブジェクトはフラット化すれば描画が速まるものの、まだ難点もある。

  • Spriteオブジェクトに子オブジェクトを加えると、Event.ADDEDEvent.ADDED_TO_STAGEイベントが配信されるので、子オブジェクトを増やすと負荷が高まる。
  • Spriteを含めたDisplayObjectContainerインスタンスには、ひとつの子オブジェクトはひとつの親インスタンスにひとつしか加えられない。

低レベルのクラスQuadBatchなら、これらの問題はなくなる。ひとつのQuadインスタンスをいくつでも加えられるうえ、いちいちイベントが配信されない。QuadBatchオブジェクトの描画は、SpriteとQuadおよびImageを含めた表示オブジェクト四天王の中で間違いなく最速!

ただし、つぎの点に注意しなければならない。

  • 加えるオブジェクトは、すべて同じ状態になる。
  • 加えられるのはImageとQuad、およびQuadBatchオブジェクトだけ。
  • 後から状態を変えたり、加えたオブジェクトを除くには、リセットするしかない。

ひとつのImageオブジェクト(myImage)を複数QuadBatch.addImage()メソッドでインスタンスに納めた例(図006)。

var myQuadBatch:QuadBatch = new QuadBatch();
var myImage:Image = new Image(myTexture);
mySprite.addChild(myQuadBatch);
myQuadBatch.addImage(myImage);
for (var i:uint = 0; i < 5; i++) {
  myImage.x += 20;
  myQuadBatch.addImage(myImage);
}

図006■ひとつのImageオブジェクトが複数描かれた
図006


04 Starlingフレームワークを最適化するActionScriptの設定

04-01 DisplayObject.blendModeプロパティをBlendMode.NONEに設定する

テクスチャが完全に不透明の矩形であれば、ブレンドをなしにするとGPUの負荷が減らせる。

DisplayObjectオブジェクト.blendMode = BlendMode.NONE

04-02 Stage.colorプロパティの設定

背景が単色のときは、テクスチャやQuadインスタンスを置くのではなく、Stage.colorプロパティで色設定する。

04-03 DisplayObject.widthやDisplayObject.heightプロパティを繰返し参照することは避ける

DisplayObject.widthDisplayObject.heightプロパティの参照は、演算の負荷が高い。インスタンスの変換行列(回転/伸縮/移動)を調べて計算するためだ。とくにSpriteに代表されるDisplayObjectContainerインスタンスでは、すべての子インスタンスについて変換行列の計算が繰返される。

// 避ける:
for (var i:int = 0; i < numChildren; ++i) {
  var child:DisplayObject = getChildAt(i);
  if (child.x > wall.width) {
    child.removeFromParent();
  }
}

// 望ましい:
var wallWidth:Number = wall.width;
for (var i:int = 0; i < numChildren; ++i) {
  var child:DisplayObject = getChildAt(i);
  if (child.x > wallWidth) {
    child.removeFromParent();
  }
}

■DisplayObject.widthやDisplayObject.heightは内部的にDisplayObject.getBounds()またはDisplayObjectContainer.getBounds()メソッドを呼出す

DisplayObject.widthDisplayObject.heightプロパティを参照すると、内部的にはDisplayObject.getBounds()メソッドが呼出され、戻り値のRectangleオブジェクトから幅または高さを返す。

public function get width():Number {
  return getBounds(mParent, sHelperRect).width;
}

DisplayObjectContainerクラスはこのメソッドをオーバーライド(override)する。そして、すべての子インスタンスの矩形領域を調べたうえで、それらが完全に含まれる矩形座標を求めてRectangleオブジェクトで返す。

public override function getBounds(targetSpace:DisplayObject, resultRect:Rectangle = null):Rectangle {
  // ...[中略]...
  for (var i:int = 0; i < numChildren; ++i) {
    mChildren[i].getBounds(targetSpace, resultRect);
    minX = minX < resultRect.x ? minX : resultRect.x;
    maxX = maxX > resultRect.right ? maxX : resultRect.right;
    minY = minY < resultRect.y ? minY : resultRect.y;
    maxY = maxY > resultRect.bottom ? maxY : resultRect.bottom;
  }
  // ...[中略]...
  resultRect.setTo(minX, minY, maxX - minX, maxY - minY);
  return resultRect;
}

04-04 インスタンスのDisplayObject.touchableプロパティをfalseにする

マウスやタッチパネルの操作をすると、Starlingフレームワークはその対象となるすべてのDisplayObjectインスタンスに対して、DisplayObject.hitTest()メソッドを呼出すので負荷が上がる。マウス操作の対象とならないインスタンスは、予めDisplayObject.touchableプロパティをfalseにしておくとよい。

// 望ましい:
var children:int = container.numChildren;
for (var i:int=0; i < children; ++i) {
  containter.getChildAt(i).touchable = false;
}

// より望ましい:
container.touchable = false;

04-05 Starling 1.2にEventDispatcher.dispatchEventWith()メソッドが加わる

引数のイベントオブジェクトを新たにつくらずに使い回しまして、イベントを配信する。ガベージコレクションの手間が減る。なお、第2引数はイベントをバブリングさせるかどうかのブール(論理)値。

object.dispatchEventWith(Event.TRIGGERED, true);

object.dispatchEvent(new Event(Event.TRIGGERED, true));

EventDispatcher.dispatchEventWith()メソッドの第3引数に任意のObject型の値を渡すと、イベントオブジェクトにそのデータが加えられる。そのために、Eventクラスには新たにEvent.dataプロパティ(Objectデータ型)が備わった。

addEventListener("customType", onEvent);
dispatchEventWith("customType", false, "testData");

private function onEvent(event:Event):void {
  trace(event.data);   // 出力: testData
}

EventDispatcher.dispatchEventWith()メソッドの第3引数に渡した値は、イベントリスナーの第2引数でも受取れる。リスナー関数の引数は、必要がなければすべて省いてしまっても構わない。

addEventListener("customType", onEvent);
dispatchEventWith("customType", false, "testData");

// version 1:
function onEvent():void {
  // 引数は要らず、イベントのみ捉えればよい
}

// version 2:
function onEvent(event:Event):void {
  // Event.dataプロパティを取出す
  trace(event.data); // 出力: testData
}

// version 3:
function onEvent(event:Event, data:String):void {
  // 引数で追加データを受取る
  trace(data); // 出力: testData
}

定義済みのイベントについてもリスナーの引数などを柔軟に定めるEventDispatcher.addEventListenerWith()メソッド(ただし、このメソッドは本講演時現在のEventDispatcherクラスには未搭載)。

addEventListenerWith(Event.ADDED_TO_STAGE, onAddedToStage);
function onAddedToStage():void {
  // イベントオブジェクトが必要?!
}

addEventListenerWith(Event.ENTER_FRAME, onEnterFrame);
function onEnterFrame(event:Event, passedTime:Number):void {
  // passedTimeで経過時間がわかる
}

addEventListenerWith(KeyboardEvent.KEY_DOWN, onKey);
function onKey(event:Event, keyCode:uint):void {
  // keyCodeがわかれば十分では
}

おまけ Starling Wikiで紹介されたActionScript 3.0最適化のコツ



作成者: 野中文雄
作成日: 2012年8月1日


Copyright © 2001-2012 Fumio Nonaka.  All rights reserved.