cocos2d-x v3から、ドローコールのオートバッチ機能が追加されました。
これによりSpriteBatchNodeを使わなくてもドローコールを纏められるようになり、
ドローコールを意識しなくてもパフォーマンスが良くなるようになりました。
そのため、ヒエラルキーに制限が生じてしまうSpriteBatchNodeを使うことは非推奨となりました。
しかしながら、このオートバッチの仕組みを知らないとドローが増えることに直結します。
特にスマホでは、数十程度のドローに抑えないとパフォーマンスが悪化します。
そこでcocos2d-x v3における、ドローコールの発行の流れ、オートバッチの仕組み、そしてZオーダーとの関係について説明したいと思います。
(質問・ツッコミ大歓迎です)
ドローコール発行の流れ
ドローコールが発行されるまでに以下のような流れになります。
- Sceneから子を深さ優先で探索する
- 各Nodeでドローコールを発行するコマンドをレンダラーキューに貯める
- レンダラーキューを、後述するグローバルZに従ってソートする
- 前後のドローコールでオートバッチを試みる
- ドローコール発行してドロー
2系までは(2)の時点でドローコールが発行されてたので、オートバッチ機能のために一旦溜めるようになった形だと思います。
そして、肝になるのは、(3)のソートの方法と、(4)のオートバッチされるために必要な条件です。
オートバッチに必要な条件
ドローコールがバッチされるためには、おおまかに以下の条件を満たす必要があります。
- テクスチャが同一
- BlendFuncが同一
他にも若干細かい話がありますが、基本的には上の条件を満たしてるドローコールが連続していればバッチされます。
2種類のZオーダーと描画順
あとはオートバッチされるべきドローコールを、いかに連続して送り込むかです。
ここでは、レンダラーがキューを以下にソートするかについて説明します。
まず、v3からは既存のローカルZの他にグローバルZという概念が導入されました。
グローバルZのおかげでエフェクトなどをヒエラルキーに依存しないで描画順を指定できるようになりました。
しかしながら逆にZオーダーの指定方法が2種類あるために、実際の描画順が分かりづらくなっています。
そこでまず、グローバルZと描画順の関係について解説したあと、ローカルZと描画順の関係について解説します。
レンダラーキューにおけるソート
ローカルZとグローバルZが混在するなかで、レンダラーキューは以下のようにドローコールをソートします。
(前提として、それぞれのZはデフォルト0です)
- ドローコールのグローバルZが負、0、正の3つグループに分ける
- グローバルZが負と正のグループについては、昇順にソートする
- グローバルZが0のグループについては、ソートしない(デフォルトはこれ)
- グローバルZが負、0、正の順に結合(したように見せる)
このように、グローバルZを指定したNodeについては、ヒエラルキーに関係なく、(グループ分けがあるものの)小さい順に描画されます。
ローカルZとドローコールの発行順の関係
前述のように、グローバルZが0、つまりデフォルトの指定ではドローコールはソートされません。
つまりレンダラーキューに追加された順にドローコールが溜まっています。
それではどういう順番でドローコールがキューに追加されているかというと、これまた最初に説明した通り深さ優先探索ですが、若干説明を端折っていますのでここで詳しく説明します。
まずSceneオブジェクトをルートとし、そこから深さ優先探索しますが、探索順序に関してはローカルZが深く影響していきます。
まず、ドローコールをキューに追加しているのはNode::visit
メソッドですが、これは以下のようなアルゴリズムになっています。
- (必要に応じて)自身の子をローカルZの昇順でソートする。(ローカルZが同じ場合は、子に追加した順)
- ローカルZが負のものについて、値が小さいものから
visit
を呼ぶ - 自身のドローコールを発行する
- ローカルZが0以上の子について、順に
visit
を呼ぶ
ローカルZが負の場合が若干ややこしいですが、基本的には深さ優先探索と思って構わないと思います。
ポイント
- グローバルZが0以外のドローコールは昇順ソートだが、破壊的なので同じグローバルZの場合のドローコールの発行順は不透明
- オートバッチの条件を満たさないドローコールを同じグローバルZで指定すると、ドローが増える可能性あり
- デフォルトはグローバルZが0なので、ソートされない
- ヒエラルキーとローカルZが大事
- ローカルZが負のNodeが、自身のドローコールより優先されるのに注意
オートバッチの対象外となるNode
幾つかの特殊ノードについては、オートバッチの対象とならずに、独自のドローを行います。
これらを使う際は、Zオーダーやヒエラルキーに注意しましょう。
- LayerColor
- TextureAtlas
- Label
- ProgressTimer
- DrawNode
- MotionStreak
- ClippingNode
- …
例
例1
同じテクスチャのSpriteであるAとBの間に、同じテクスチャのProgressTimerであるCを挟んだ。
↓
AとBはバッチされません。
例2
同じテクスチャのLabelを並べた。
↓
バッチされません。(回避不可能)
簡単な問題
ここで簡単な問題を出してみたいと思います。
問題
解
余談
将来的にレンダラーはマルチスレッドになる計画です。
参考
- 本家Wiki
- ソース