ラベル actionscript3 の投稿を表示しています。 すべての投稿を表示
ラベル actionscript3 の投稿を表示しています。 すべての投稿を表示

2011年6月14日火曜日

シェイプトゥイーンの実験コードを書いてみた。

早速ですが、シェイプトゥイーンをスクリプトでというものを作ってみました。最適化まではやっていないので、ちょっと重たいかもしれませんが、そこそこに動いています。



考え方としては、パスをIllustratorでいう複合パスという概念で扱っています。FlashのGraphicsPathは、閉じようが、閉じまいが、移動しようが、連続してようが、あくまで1つのパスです。しかしモーフィングにおいては、前後でマッチングさせる単位が分からなくなるため、閉じたパスの集合として管理しています。

前後のパスの補間は、閉じたパスをお互いに列挙し、
  1. 開始のパスがあって終了のパスがない場合は、開始のパスのバウンズの中心を基準にする
  2. 終了のパスがあって開始のパスがない場合は、終了のパスのバウンズの中心を基準にする
  3. 片方しかない場合は、基準点に同じ数のセグメントのパスがあるもとのみなす
  4. 開始終了両方とも、セグメントの数が同じ場合は特に加工しない
  5. セグメント数が違う場合は、セグメント間の距離の割合が、両方のパスで同じになるようにセグメントを分割する
という感じです。

5はもうちょっと具体的に言うと、パスの距離を1としたときに、開始パスのアンカーポイントの位置が[0, 0.2, 0.4, 0.6, 0.8]とあって、終了パスのポイントの位置が[0, 0.1, 0.5, 0.9]とある場合、両方のパスのアンカーポイントの場所が、それぞれ[0, 0.1, 0.2, 0.4, 0.5, 0.6, 0.8, 0.9]になるように調整しています。Illustratorでいうアンカーポイントの追加になるます。

1~3は、そもそもパスがないですから、ある方の中心部分にパスが埋れているとみなすようにしています。ある方のパス、ない方のパスで、それぞれ同じ数のセグメントになるように、中心の座標のみを使ってmoveTo/curveToを繰り返します。

あとは、直線もすべてカーブで扱うようにしています。直線の場合は、始点と終点の50%の所にコントロールポイントが来るようにしています。これならあんまり考えることなく、カーブと直線の補間が可能です。

まだ作りかけで、線や塗にも対応させようと思います。

2011年6月12日日曜日

開発再開

アニメーションライブラリの開発を再開しました。
ずっと仕事が忙しかったので、殆ど何もできずにいましたが、
最近やっと落ち着いてきました。

アニメーションライブラリ自体の開発はほぼ終わってるのですが、
テストやチュートリアルの作成が全然というところです。

ところで、シェイプトゥイーンをスクリプトで書けるというのは需要があるのでしょうか。
まだ、具体的に練ってはいないですが、

var path1:Path = new Path();
path1.curveTo(...);
path1.curveTo(...);
path1.curveTo(...);

var path2:Path = new Path();path2.curveTo(...);
path2.curveTo(...);
path2.curveTo(...);

var path3:Path = Path.interpolation(path1, path2, 0.5);

という感じで書けるとパスのモーフィングができるという感じで考えています。

直線は開始点と終了点の中間に制御点が置かれているカーブで表現して、
あとは全てのセグメントの制御点、始点、終点の位置を、
開始と終了の間で補間すれば表現できるのではと思っています。

ただし、開始と終了でパスの数が合わない場合に、どうやってパスの数を水増しするかが問題です。
数学が苦手なのでこういうのは辛いですね。

2011年1月16日日曜日

Flash用のPixelBenderはちょっと微妙

あくまで主観です。
  1. デバッグできない
  2. ツールキットがスペース含むディレクトリに保存できない
  3. 思ったほど速くない
  4. ShaderJobが再利用できない
  5. GPU使ってくれない
  6. FlashBuilder上で開発できない
ってところでしょうか。デバッグビルドとリリースビルドで速度差があるので、
基本的にGPUではなくFlashPlayerで計算しているのでしょうか。
次期FlashPlayerのmolehillだとGPU使えるようなので、GPGPUもできるようになるのでしょうか。
100万パーティクルが60fpsで動かせるのを心待ちにしています。

ちなみに速くないといっても、同じ処理を行うAS3のコードよりは速いです。自分の環境で2倍強くらい。
4つの値を1パーティクルとして、パーティクルを100万(Vectorで400万要素)用意して、PixelBenderに投げても、100msから150msくらいはかかってました。AS3なら200msから300msくらい。
でも、パーティクル情報を更新しつつ、ビットマップにも書きつつということを同時にやってくれないので、トータルするとそんなにコストは変わりません。

セキュリティーホール??

FlashのLoaderクラスは、crossdomain.xmlが設置されていなくても、クロスドメインで画像が読み取れます。
まぁ、これはいいでしょう。ただし、このLoaderをBitmapDataに転写したりはできません。
が、実は抜け穴があります。以下をご覧ください。

外部画像をビットマップ化する - wonderfl build flash online


操作できてますね。ここで思ったのが、「外のサイトのHTMLをLoaderInfoのbytesから取得できるのか」です。
Loaderとしては当然画像やSWFではないですからエラーになりますね。しかしbytesが取得出来れば、文字列化してHTMLを解析することができます。
そこで以下を見てください。

ストーキング - wonderfl build flash online


forked from: Twitterのストーキング - wonderfl build flash online


結論からいうと、bytesは0バイトですが、totalBytesは0ではありません。何バイト読めたかがわかります。
つまり、ログインしている場合としていない場合で、HTMLのサイズが大きく違うようなサイトの場合、
ユーザがログインしているかを判断することができてしまいます。

1つの広告で、Mixiを使っている人向けの広告と、使っていない人向けの広告を出し分けるみたいなことができてしまいます。
これってセキュリティホールですよね・・・??

2011年1月6日木曜日

MatrixのtransformPointの最適化

Twitterの方にもつぶやいたのですが、MatrixのtransformPointは、Matrixの各要素を元に直接求められることに気づいたのでメモ。
var m:Matrix = ...;
var x:Number = ...;
var y:Number = ...;
x = x * m.a + y * m.c + m.tx;
y = x * m.b + y * m.d + m.ty;
通常だと入力用にPointのインスタンスを作る必要がありますが、最適化としてPointを使い回すというやりかたもあります。でも戻り値が新しいPointクラスなので、使用メモリが増えるし、生成コストが馬鹿になりません。
この方法ならa/b/c/d/tx/tyを使い回せば、大量に座標変換しても高速に計算できます。

ベジェのパスでアニメーションできる機能を追加した

ベジェのパスでアニメーションできる機能を追加しました。距離の割合を元に座標を求めているので、きつい曲線などでも等速でアニメーションできます。
ライブラリの機能としては、曲線と直線の組み合わせの情報を元に、t(0.0から1.0)に対してセグメントの距離と全体の距離から、x/yを求めるようになっています。Matrixの適用も可能です。
そのパスライブラリを使ったサンプルをWonderflにアップしました。

ベジェと直線のパス上を等速でアニメーション - wonderfl build flash online


参考元はこちらのサイトです。実際のところ数学は全く苦手なので、何やってるかわかりません。移植して最適化しただけです。
目標としてはSVGの命令を網羅したいところなのですが、積分が全くダメなので円弧の楕円積分などで躓くでしょう。。。

2011年1月1日土曜日

アニメーションライブラリの進捗具合

今開発中のアニメーションライブラリの進捗具合についてです。

開発も進んで、相当速くなってきたのと、メモリの使用効率もそこそこによくなってきました。コア機能については、そこそこに充実してきましたので、一旦開発を止めて、Tweener互換ライブラリに着手しようと思っています。Tweener互換機能が実装できたらテストを行って、version1.0として初リリースを行おうと思います。

各Tweenライブラリの開発元で用意しているベンチで比べても、同等もしくは高速という感じになってきたので、パフォーマンスは魅力的な部分だと思いますが、いまどき速いくらいじゃ魅力もないので、魅力的な機能を入れていかないと使ってもらえないだろうなと思っています。

まず1つ目がTweener互換機能。TweenerのSWCと入れ替えるだけで、今までのコードを修正することなく、アニメーションのパフォーマンスが上がるというもの。特に、大量のアニメーションを並列で動かすときに、差が出ると思います。逆に1つのオブジェクトを動かすくらいなら大差はないと思います。

ただし、完全互換は難しいと思っています。そもそもベースが違うし。TweenerクラスとSpecialPropertyとAuxFunctionsクラスは残すことになると思いますが、それ以外は残さないと思います。もし、拡張ライブラリ等でPropertyInfoObjとかアタッチするようなことがあれば、動かないでしょう。TweenerクラスとSpecialPropertyクラスだけ使っていれば、基本的に問題ないようにしようと思っています。

あと、もう一つ特徴的な機能としてFlex対応というところ。これもまだ未着手ですが、コアの部分では対応済みです。

Flexの場合、通常は24fpsで動かすことになりますが、アニメーションで24fpsは正直スムーズな動きとは言えません。ぬるぬる感を出そうとすると60fpsくらいが丁度いいです。そこで、通常時は24fps、アニメーション時はもっと高いフレームレートを出すために、通常のタイマーからFlex向けのタイマーへの差し替え機能を提供しています。

通常時はDisplayObjectクラスのENTER_FRAMEによるタイマー処理を行っていますが、TimerクラスのTIMERイベントによるタイマー処理を行うように変更できます。このタイマーは標準で10ms単位でイベントを呼び出すようにしており、またTimerEventのupdateAfterEventの呼び出しを行いますので、24fpsでも24fps以上のフレームレートで再描画が行われます。

Tweenの高速化についてまとめ。その4

年も変わったんで、文体変えてみようかな。ちょっと固い感じがする。

と、さておき、Tweenの高速化についてまとめてみましたが、徐々に実際のコードをベースに、細かいところについて書いていこうと思います

まずは、以下のようなリンクリストのノードがあったとします。
public class Node{

    public var next:Node;

    public var val:uint;

    public function Node(val:uint, next:Node){
        this.val = val;
        this.next = next;
    }

    public function any():void{
        trace(this.val);
    }
}
では、これを処理するコードはというと、
//リンクリストを処理する関数の中と仮定
//_headはリンクリストの先頭のprivate var の値

//whileの場合
var node:Node = _head;
while(node !== null){
    node.any();
    node = node.next;
}

//forの場合
for(var node:Node = _head; node !== null; node = node.next){
    node.any();
}
という感じになると思います。もしも、「nodeが仕様上1つ以上ではあるが、実態としては1つのケースが多い」と仮定した場合、for/whileをすっ飛ばすことによって最適化することができます。
//リンクリストを処理する関数の中と仮定
//_headはリンクリストの先頭のprivate var の値

//whileの場合
if(_head.next === null){
    _head.any();
}else{
    var node:Node = _head;
    while(node !== null){
        node.any();
        node = node.next;
    }
}

//forの場合
if(_head.next === null){
    _head.any();
}else{
    for(var node:Node = _head; node !== null; node = node.next){
        node.any();
    }
}
できれば、anyという関数も、呼び出し元の所にインライン展開してしまったほうが、もっと高速に動きます。業務ロジックを1箇所に固めたほうが当然メンテナンス性も可読性も良く、品質も管理しやすいですが、冗長でも関数呼び出しを減らすことによって高速に動作します。関数呼び出しのオーバーヘッドは馬鹿になりません。

2010年12月26日日曜日

Graphicsのクリア忘れに注意

業務でやってたシステムで発生した問題。Flexで独自コンポーネントを作ったときに、updateDisplayListでGraphicsオブジェクト捕まえて、beginホゲホゲなどdrawホゲホゲなどやるのはよくあることだと思う。

その時に気を付けないといけないのが、Graphics.clear()の呼び忘れ。特に透明なしのベタ塗りだと、見た目は全く一緒なので、全く気づかないと思う。

では何が起こるかというと、描画コストがupdateDisplayList毎に増えていく。特にBitmapData.drawをするとよくわかる。簡単に言うと、ベタ塗り100回clearせずに行えば、BitmapData.drawを呼び出したときに、BitmapDataに100書き込まれるわけだ。

Flexでアニメーションを行おうとすると、領域が大きく、また子要素も多いと、フレームレートが低くなりがちである。その際に役に立つのが対象のオブジェクトを一旦BitmapDataに書きこんで、Bitmapをアニメーションさせるのである。フェードでも役に立つ。

開発しているシステムでは、これが徐々に遅くなり、イベントからアニメーション開始まで200msくらいフリーズした状態となっていた。しかも、連続運用4時間くらいから傾向が出始め、プロファイラで見てもメモリは増えてない。見つけるまで一週間かかった。みんなも気をつけましょう。。。

2010年12月24日金曜日

Tweenの高速化についてまとめ。その3

また内容がコアな感じになるが、次はJITの話。

アニメーションライブラリにエラーハンドリングを入れる際に気づいたのが、JITを殺すようなコードを書いてはいけないということ。そもそもFlashにおけるJITとは何か。それはバイトコードをインタプリタで動かしている状態で、よく使われる箇所をネイティブコードに動的に置き換えてしまう技術である。正式な名前はJust In Time Compilerである。

ではJITを殺すようなコードとは何か。まずはサンプルを見たほうが早い。

try/catchによってJITが効かなくなる件の検証 - wonderfl build flash online

大きく違うのが、関数の中にtry/catchが含まれているかの部分である。実行されるかは関係ない。他に検証してみたが、with句と関数定義が含まれているケースでも、JITが効いていないのが確認できた。

逆に言うとtry/catch/with/functionが含まれていなければJITの対象となるようである。上記コードでもtry/catchの部分だけ関数化した検証も含んでいるが、JITが効いているようである。

確認はしていないが、コンストラクタもJITは効かないようである。「関数呼び出しの時間」 < 「JIT無効の処理時間」 - 「JIT有効の処理時間」を満たすようなコード、簡単に言うとコンストラクタが忙しいコードの場合は、コンストラクタ内に書いているコードを初期化関数の呼び出しに変えて、初期化関数内で忙しい処理を行えば良い。ただし、関数呼び出しのオーバーヘッドを考慮する必要がある。

とは言え、最適化の話であるので、呼ばれる頻度の低いケースに置いては可読性や可用性を重視した方がよい。最適化原理主義者になってはいけない。

2010年12月22日水曜日

アニメーションライブラリのアルファ版を公開

アニメーションライブラリのアルファ版を公開した。URLはこちら。

コアについては、高速化の為に恐ろしく気持ち悪いコードになっていて、publicやらinternalのvarが沢山ある。OOP狂信者からすると死刑レベルである。これは高速化の為にやむを得なくやっているわけであって、決してOOPよくわかりませ~ん。というわけではない。

カプセル化が美しいコードとよくいうが、カプセル化のために関数経由で変数を参照していたら日が暮れてしまうのだ。AVM2はそんなに速くはない。

そこで、ファクトリとなるクラスを導入して、コア実装部分はinternalクラスとして外部からは不可視とし、インターフェイスを経由して外部から操作できるようにしている。コア実装部分ではアニメーションマネージャとアニメーションオブジェクト同士が、密にpublicなvarを直接参照しあっているのだが、外部からはインターフェイスを経由してget/setでしか触れないようにしている。中が汚くても外には見せてはいけない。

クラス数はそこそこに多いのだが、外部から見えるのはインターフェイスばっかりで、コンクリートクラスは少ないと思う。拡張性に難があるのだが、プラグイン的な機構は用意しているし、増やしていこうと思う。直接コアを拡張したいなんてニーズは少ないと思うし。

当面としては、基本機能のテストをしながら、Tweenerと同じパッケージ名、同じクラス名のAPIを提供したいと思っている。SWCを差し替えるだけで高速化できるというだけで、かなりなニーズがあると思う。

2010年12月20日月曜日

Tweenの高速化についてまとめ。その2

前回はリンクリストの手法について書いたけど、次は使用メモリについて。

アニメーションを高速化させる上で、高速化の邪魔となるのがGCだったりする。GCによって、非到達オブジェクトのマーキング、マークされたオブジェクトの破棄、領域のデフラグメントなどが行われる。60fpsのアニメーションの途中でGCが発生すると、60fpsを一瞬切ることもあり、体感としても一瞬詰まる感じもする。

次に試してみて欲しいのが以下のコードである。

プロパティの設定方法によって、メモリの利用量が変わる件のテスト - wonderfl build flash online


どう見ても同じオブジェクトの同じプロパティにNumberの値を代入しているだけでも、プロパティが静的に解決できているケース以外、代入するたびにメモリーが増えているのだ。これは不可解である。ちなみに代入する値がStringだと発生しない。また、プロパティが静的に解決できるケースにおいても全く増えない訳ではないが、微々たるものである。

仮に10000パーティクルを動かすとすると、毎フレームごとに数十から数百KBytesずつ消費され、1秒間だけでもあっという間に数十MBytes消費してしまう。実際にはリークしているわけではないので、GCされるが、当然重くなる。

今作成中のアニメーションのライブラリについては、動的な型と名前から、静的なプロパティへの代入処理にマッピングしなおすことによって、代入時のメモリ増を押さえてある。

2010年12月17日金曜日

Tweenの高速化についてまとめ。その1

最近作っている、そこそこに高速なTweenのライブラリについて、主に高速化の手法についていくつかまとめ。

まずは定番ではあるが、配列を使わずにリンクリストを使う。やはりFP10.1ではVectorよりも速い。ただし、要素の削除に伴う付け替えのコストが高い。削除による要素の付け替えの場合を考えてみると、以下の処理が必要となる。
  • 削除対象の前がnullなら次の要素がヘッドになる
  • 削除対象の次がnullなら前の要素がテイルになる
  • 削除対象の前後両方がnullならヘッドもテイルもnullになる
  • 削除対象の前後両方がnullでなければ、前後を繋ぎ合わせる
IF文は高速化の邪魔であるので、まずは不要な判定を減らしたいところである。そこで、ヘッドもテイルもダミーの要素として、事前に用意するのである。すると削除対象の前後が絶対にnullにならないので、IF文は不要になるのである。

リンクリストはwhile文などで回すことが多いと思うが、ループ毎にループを抜ける判定が必要になる。処理の実行回数にたいしてループの判定を減らすことによって高速化ができる。その手法として、ループの展開(アンロール)と呼ばれる手法がある。

例えばループを1ループあたり8回処理するようにすると、ループ回数は8で割った数になる。では処理回数が8で割り切れない場合はどうするか。普通に考えると、ループ内で都度、「まだ処理は必要か」と判定を行うことになる。それではアンロールの効果はないし、本末転倒である。

そこで、リンクリストのテイルに着目してみよう。テイルの次の要素は最後なのでnullである。しかし、テイルの次の要素がテイル自信の場合は、次、次と辿っても、nullが現れることはないのだ。つまり、nullであるかの判定が不要なので、「まだ処理は必要か」という判断がいらないのである。

次回はメモリ関連について書いてみる予定。

2010年12月12日日曜日

Math.powでの高速化について

Math.pow(n, 2)と書くくらいなら、n*nと書いた方が速いのは周知のとおり。では、Math.pow(n, m)の場合はどうか。mはuintとするが不定である。

累乗の計算のパフォーマンスの比較 - wonderfl build flash online


結果からすると、Math.powの方が圧倒的に遅い。しかしデバッグ実行では、ifよりswitchの方が遅く、また5乗あたりからMath.powの方がif/switchよりも速くなる。三項演算子は速いまま変わらない。

JITの特性の問題かもしれません。