StarlingのテクスチャをBitmapDataではなくてByteArrayでアップしてみる

もう日が超えてしまいそうですが、[Adobe AIR Advent Calendar 2013](http://www.adventar.org/calendars/182) 5日目の記事です。

AIR for mobileではStage3Dはほぼ必須な技術かと思いますが、[Starling](http://gamua.com/starling/)を使う際のテクスチャのアップロードをByteArrayからやる方法について試してみました。

きっかけとしては、「[diadraw-air-camera-native-extension](https://code.google.com/p/diadraw-air-camera-native-extension/)」のiOS用のANEを使って取得できるカメラの画像がByteArrayだったので、BitmapDataへの変換を挟まずに直接アップロードできたら処理が早くなるかな～っていう所から始まっています。

ちなみに、ANEの使い方は「[AIR for iOSでANEを使ってNativeCameraを扱う](http://qiita.com/masamunet/items/52d03d1cacaba2c31783)」にまとまっていますので参考にされると良いと思います。

では、本題。
とりあえず、コードを丸ごと紹介します。

```as3
static public function getRectangleTextureFromByteArray(data:ByteArray, width:int, height:int, optimizeForRenderToTexture:Boolean=false):Texture
{
	// Context3Dを取得
	var context:Context3D = Starling.context;
	
	// エラー
	if (context == null) throw new MissingContextError();
	if (Starling.current.profile == "baselineConstrained" || !("createRectangleTexture" in context)) 
		throw new Error("RectangleTexture is not supported for this Flash Player version.");
		
	// ネイティブのテクスチャを生成
	var nativeTexture:TextureBase = 
		context["createRectangleTexture"](width, height, "bgra", optimizeForRenderToTexture);
		
	// ネイティブのテクスチャを使って Starling の ConcreteTexture インスタンスを生成
	var concreteTexture:ConcreteTexture
		= new ConcreteTexture(nativeTexture, "bgra", width, height, false, true, optimizeForRenderToTexture, 1);
	
	// ネイティブのテクスチャ経由で ByteArray をアップロード
	nativeTexture["uploadFromByteArray"](data, 0);
	
	// Context3Dの再作成時のリストア処理
	concreteTexture.onRestore = function():void
	{
		concreteTexture.clear(); // mDataUploaded を true にするためだけに実行
		nativeTexture["uploadFromByteArray"](data, 0);
	};
	
	return concreteTexture;
}

static public function uploadFromByteArrayForRectangleTexture(concreteTexture:ConcreteTexture, data:ByteArray):void
{
	// ネイティブのテクスチャを取得
	var nativeTexture:TextureBase = concreteTexture.base;
	
	// ネイティブのテクスチャ経由で ByteArray をアップロード
	nativeTexture["uploadFromByteArray"](data, 0);
	
	// Context3Dの再作成時のリストア処理
	concreteTexture.onRestore = function():void
	{
		concreteTexture.clear(); // mDataUploaded を true にするためだけに実行
		nativeTexture["uploadFromByteArray"](data, 0);
	};
}
```

`getRectangleTextureFromByteArray()` が最初にTextureを生成してByteArrayをアップロードするメソッドで、 `uploadFromByteArrayForRectangleTexture()` が生成したTextureのByteArrayを更新して再アップロードするメソッドになります。

一応、Starlingのソースを見ながら他と同様に処理されるように書いたので、動作に問題は無いはず…。
リストアの所だけまだ検証してないですが…。

<br />

ポイントは2つありまして。

#### 1. 決めうちでConcreteTextureを作成する
`Texture.fromBitmapData()` などで画像からTextureを生成して取得する場合、画像サイズなどの条件に応じてConcreteTextureかSubTextureのどちらかが返ります。

以下の条件に当てはまる場合はConcreateTextureを返します。

1. 縦横のサイズが2の冪
<br />→ 裏で `flash.display3D.textures.Texture` を使用する
2. 縦横のサイズが2の冪でない & ミップマップを使用しない & RectangleTextureがサポートされている（FlashPlayer 11.8/AIR 3.8以降）
<br />→ 裏で `flash.display3D.textures.RectangleTexture` を使用する

今回はカメラから取得した画像をアップロードする前提だったので、2の条件に当てはまるようにして直接ConcreteTextureを作成しています。

ちなみに、SubTextureではテクスチャのサイズを2の冪に合わせた上で透明領域で穴埋めした画像が使われるようです。
SubTextureは元の画像の領域を保持し、サイズ合わせをした画像のConcreteTextureを作成して内部に保持するみたいな感じです。

#### 2. リストア時の処理を追加
Context3Dが一度ロストして再作成された際には、テクスチャの再アップロードが必要になります。
その際の処理は `ConcreteTexture.onRestore` に渡した関数に記述しておきます。

ここで注意が必要なんですが、ConcreteTextureはprivateでmDataUploadedというフラグを持っていて、テクスチャを再アップロードした際にこのフラグをtrueしています。
 `ConcreteTexture.onRestore` の処理が終わった後にこのフラグがtrueになっていない場合、 `concreteTexture.clear()` というメソッドを呼び出して透明なテクスチャを生成してアップしてしまうようです。
（恐らく、テクスチャが空のままでエラーが起こるのを防ぐためだと思います。）

mDataUploadedはprivateなので外からは制御できないですが、 `concreteTexture.clear()` を呼び出すと内部でこのフラグがtrueになるみたいなので、先に呼び出してから実際のテクスチャをアップするようにしています。
これで、透明なテクスチャで上書きされるのを防ぐことができます。

<br />

さて、ここまで意気揚々と説明してきましたが、実際にこれを使って先ほどのANE経由でカメラの画像を取得するiOSアプリを作って試してみました。

<br />

が、正直、そこまで違いは無いような気がしました。。。
気持ち、速くなったかなぁと。

<br />

もうちょっと、ちゃんとベンチマーク取ってみたり使えそうなケースが無いか検証してみようと思います。

せっかく作ったので、取り急ぎ公開してみましたというお話です。