More Related Content Similar to FFRK cocos2d xレイヤーの最適化 (20) FFRK cocos2d xレイヤーの最適化1. FINAL
FANTASY
Record
Keeper
cocos2d-‐xレイヤーの最適化
株式会社ディー・エヌ・エー
Japanリージョン
ゲーム事業本部
技術・編成部
開発基盤グループ
惠良良
和隆
kazutaka.era@dena.com
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
2. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
⾃自⼰己紹介
! 惠良良和隆(えら
かずたか)
⁃ 2002年年
株式会社フロム・ソフトウェア⼊入社
• コンソールゲームの開発(クライアント、サーバー)
• ライブラリ、フレームワークの開発
• 開発環境構築
• etc
⁃ 2013年年10⽉月
株式会社ディー・エヌ・エー⼊入社
• ゲームアプリ開発に必要なライブラリやフレームワーク、
サーバーなどの開発するグループに所属
• ゲーム開発のワークフロー整備
• cocos2d-‐‑‒xをベースとした社内ゲームエンジンの開発
• ゲームシステムのアーキテクチャアドバイザー
• etc
3. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
本⽇日のお題
FFRKのエンジンであるKickmotorの
cocos2d-‐xレイヤーに関する最適化の話
4. ご注意
! このスライドの内容は、cocos2d-‐‑‒xに関して基本的な知識識があることを
前提としています。
! cocos2d-‐‑‒xの詳細については、公式ページを参照するか、Google先⽣生
に尋ねてみてください。
! 公式ページ:http://www.cocos2d-‐‑‒x.org
! Github:
https://github.com/cocos2d/cocos2d-‐‑‒x/tree/cocos2d-‐‑‒x-‐‑‒2.2.6
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
5. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
おさらい
! Kickmotorって?
⁃ D.O.T、三国志ロワイヤルと使われている
DeNA内製フレームワーク
⁃ WebViewとcocos2d-‐‑‒xを組み合わせたハイブリットアプリを実現
⁃ アニメーションに関してはよりリッチなものを表現するために、
cocos2d-‐‑‒xの上に独⾃自のランタイムを実装し、内製ツールを使った
データドリブンな開発を実現
⁃ コンテンツプロキシ
⁃ etc
⁃ 第⼆二回勉強会のスライドに、もう少し詳しく載っています
• http://www.slideshare.net/dena_̲study/20141111-‐‑‒seminar-‐‑‒eisuke
• http://www.slideshare.net/dena_̲study/20141111-‐‑‒dena-‐‑‒study21
6. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
なぜ最適化が必要だったのか?
! CBTの散々たる結果
⁃ 重い(超もっさり)
⁃ 熱い
⁃ 充電速度度
バッテリー消費量量
! WebViewとcocos2d-‐‑‒xのどちらも重い
⁃ パーティ編成などはWebView
⁃ バトルはcocos2d-‐‑‒x
! 開発時にはiPhone5Sなどのハイスペック端末で確認
⁃ 低スペック端末でのチェックを怠っていた
8. cocos2d-‐xは、C++によるネイティブ実装
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
⇓
C++実装経験に⻑⾧長けたコンソール出⾝身者
である私に⽩白⽻羽の⽮矢が!!
r、ノVV^ー八
、^':::::::::::::::::::::::^vィ 、ヽ l / ,
l..:.::::::::::::::::::::::::::::イ = =
|.:::::::::::::::::::::::::::::: | ニ= 仙 そ -=
|:r¬‐--─勹:::::| ニ= 道 れ =ニ
|:} __ 、._ `}f'〉n_ =- な. で -=
、、 l | /, , ,ヘ}´`'`` `´` |ノ:::|.| ヽ ニ .ら. も ニ
.ヽ ´´, ,ゝ|、 、, l|ヽ:ヽヽ } ´r : ヽ`
.ヽ し き 仙 ニ. /|{/ :ヽ -=- ./| |.|:::::| | | ´/小ヽ`
= て っ 道 =ニ /:.:.::ヽ、 \二/ : | |.|:::::| | /
ニ く. と な -= ヽ、:.:::::::ヽ、._、 _,ノ/.:::::| | /|
= れ.何 ら -= ヽ、:::::::::\、__/::.z先.:| |' |
ニ る と =ニ | |:::::::::::::::::::::::::::::::::::.|'夂.:Y′ト、
/, : か ヽ、 | |::::::::::::::::::::::::::::::::::::_土_::| '゙, .\
/ ヽ、 | |:::::::::::::::::::::::::::::::::::.|:半:|.ト、 \
/ / 小 \ r¬|ノ::::::::::::::::::::::::::::::::::::::::::::::::| \
9. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
cocos2d-‐xレイヤーの問題とは?
! cocos2d-‐‑‒x
⁃ イケてないC++実装
⁃ cocos2d-‐‑‒xの都合を理理解して実装しないと性能が出ない
⁃ 凝ったことをやろうとすると性能が出ない
! 内製アニメーション再⽣生エンジン
⁃ ⾃自由度度を持たせるための汎⽤用的な実装
⁃ 汎⽤用性を上げるとオーバーヘッドも⼤大きくなる
⁃ 実⾏行行効率率率を意識識していない実装
! アニメーションデータ
⁃ 表現⼒力力と⽣生産性だけを追求したデータ
⁃ 実⾏行行効率率率が全く考慮されていない
11. 最適化⽅方針
! CBT終了了から正式リリースまで時間的余裕は無い
! 出来る限りリスクが低いコード修正に絞りたい
⁃ 複数プランを出し、⼿手軽で効果の⾼高そうなものから着⼿手
! ⼤大規模なデータ改修は避けたい
! ベンチマークとなる端末(iOS、Android)を決める
⁃ iOS:iPhone4S
⁃ Android:Galaxy
S2(Android2.3系のもの)
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
! 最適化作業はiOS端末で⾏行行う
⁃ Androidに⽐比べてプロファイラが充実している
⁃ ネイティブコードのデバッグのし易易さ
12. 最適化⼿手順
1. コンパイラによる最適化が有効なビルドを準備
2. プロファイラで情報収集(Xcode、Instruments)
3. ボトルネックの候補を抽出
4. 負荷原因を調査
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
a) コードを⾒見見て推測
b) 推測を元にコード修正
c) パフォーマンスの変化をチェック
5. 正式なコード修正
6. 1.に戻る
20. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
具体的なボトルネックの例例
! 頂点数0のドローコール
! ⾒見見えないスプライト描画
! ありえないほど多い描画パス
! 無駄なOpenGL
ES
API呼び出し(DrawCall以外)
! 透明部分の多いスプライト描画
! ⼤大量量のCCNodeで構成されたシーングラフ
! cocos2d-‐‑‒xのダメ実装
! 画⾯面外への描画
! ⼤大量量のドローコール数
25. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
⾒見見えないスプライト描画
頂点情報を確認してみると、
RGBAが全て0になっていると分かる
29. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
ありえないほど多い描画パス
毎フレーム、20回もglClear()をコール!
32. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
ありえないほど多い描画パス
! マスクノードとは?
⁃ ⾃自⾝身の⼦子孫ノードをテクスチャに描画し、
そのテクスチャに対してαマスクを適⽤用する独⾃自拡張ノード
! 便便利利なので・・・と多⽤用された結果、描画パスが20に!
マスクノード禁⽌止令令発令令!!
本当に必要なところでしかマスクノードは使わない。
バトル画⾯面で使われているものは、
マスクノードでなくても良良いものだった。
これらを削ることで、描画パスは1になった。
34. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
無駄なOpenGL
ES
API呼び出し(DrawCall以外)
問題となっている箇所は全て独⾃自拡張したコードで、
cocos2d-‐xのOpenGLステートキャッシュを使っていない
⇓
ステートキャッシュを使うように修正
41. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
CCNode::visit()の何を削ったか?
! 描画すべきノードが配下に無ければ何もしない
⁃ 画⾯面上の座標を指定する⽬目的で、多くのCCNodeがレイアウトされ
ている(実際に描画ノードを配下に持たない)
⁃ シーングラフを構築する際に、配下のノードにCCSpriteなどの描
画されるノードが存在しているかを記録しておく
• CCNode::onEnter()/CCNode::onExit()を活⽤用
⁃ 描画されるノードが配下に存在する場合のみ通常処理理
! アフィン変換⾏行行列列が単位⾏行行列列ならば⾏行行列列スタック操作をしない
⁃ グルーピングのためだけにCCNodeを使うことは多い
⁃ 多くのノードでアフィン変換による座標変化が無い
⁃ 単位⾏行行列列は掛けても結果が同じなのでそもそも掛けない
⁃ ⾏行行列列が変化しないのでスタック操作もしない
42. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
cocos2d-‐xのダメ実装
! メモリコピーのコストを考えていない!
⁃ サイズの⼤大きな構造体を関数の戻り値で返す
⁃ サイズの⼤大きな構造体を関数に値渡しする
! 関数呼び出しのコストを考えていない!
⁃ 無駄に仮想関数定義されているのでインライン展開されない
⁃ MFCかよ!?とツッコミたくなるクラス階層
! double精度度の算術関数の使⽤用
⁃ cos()/sin()などを多⽤用しているがcosf()/sinf()で⼗十分な場合が多い
⁃ 計算コストが全然違う!
! 割り算の回数を意識識していない
! D$のことを意識識していない
⁃ 連続アクセスするデータは、連続したメモリ領領域に配置すべき
⁃ cocoa互換なクラス群がイケてない
44. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
画⾯面外への描画
! バウンディングボックスの計算負荷も無視できない
⁃ 1つのエミッタから放出されるパーティクルは数百〜~数千になる
⁃ FFRKのスクロールの仕様を考慮し、X軸⽅方向だけに限定してバウ
ンディングボックスの計算を⾏行行うようにした
スクロール⽅方向
45. 画⾯面外への描画
! ワールド座標系への変換⾏行行列列(画⾯面内にあるかを判定するために必要)
を求める必要がある
⁃ ただし、CCNode::nodeToWorldTransform()の使⽤用は論論外
⁃ 全てのノードが親ノードを辿りながら、アフィン変換⾏行行列列の乗算を
⾏行行ってしまうと相当な計算コストがかかる
⁃ ノード階層が深くなるほどコストが増加する
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
CCAffineTransform CCNode::nodeToWorldTransform()
{
CCAffineTransform t = this-nodeToParentTransform();
for (CCNode
*p = m_pParent; p != NULL; p = p-getParent())
t = CCAffineTransformConcat(t, p-nodeToParentTransform());
return t;
}
47. ⼤大量量のドローコール数
! 弊社内製ツールのAnimationBuilderや
CocosBuilderなどのオーサリングツールでデータ
が作られる場合、データの作りやすさや調整のし易易
さが重視されたデータ構造になる
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
例例)・Root(CCNode)
・Charactors(CCNode)
・Char1(CCNode)
・CharIcon(CCSprite)
・CharStatusIcon(CCSprite)
・GaugeBase(CCScale9Sprite)
・GaugeBar(CCScale9Sprite)
・GaugeFrame(CCScale9Sprite)
・Char2(CCNode)
・・・
48. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
ドローコール数を減らす⽅方法
! CCSpriteBatchNodeは利利便便性が低い
⁃ CCSpriteBatchNode直下にCCSpriteを並べる必要がある
⁃ データ構造に強い制限を掛けてしまう
⁃ デザイナーにバッチ描画を意識識させるのは
ナンセンス
! バトルシーンのドローコール数は、画⾯面外描画を
除去した後でも150程度度ある
⁃ 100以下にしないと低スペック端末で重い
49. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
どうやってドローコールを削るか?
! 結論論
⁃ ドローコールをまとめて全体の数を減らす
(バッチ描画)
⁃ ただし、CCSpriteBatchNode以外の⽅方法で!
・・・という訳で、
独⾃自のバッチ描画機能を実装しました!
50. バッチ描画の要件
! 以下の条件が揃えば、バッチ描画できる
⁃ 参照テクスチャが同じ
⁃ αブレンド設定が同じ
⁃ シザリング設定が同じ
⁃ シェーダ(頂点、フラグメント)が同じ
! この条件は、OpenGL
ESの仕様によるもの
! 描画処理理の途中で変更更出来ないパラメータを揃える
必要がある
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
51. 異異なるノード階層にある描画をまとめる
! ノード階層が異異なる
→ワールド座標系へのアフィン変換⾏行行列列が異異なる
→頂点シェーダに渡すMVP⾏行行列列が違う
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
uniform
mat4
CC_MVPMatrix;
aUribute
vec4
a_posiWon;
aUribute
vec2
a_texCoord;
aUribute
vec4
a_color;
varying
vec4
v_fragmentColor;
varying
vec2
v_texCoord;
void
main()
{
gl_PosiWon
=
CC_MVPMatrix
*
a_posiWon;
v_fragmentColor
=
a_color;
v_texCoord
=
a_texCoord;
}
52. 異異なるノード階層にある描画をまとめる
! 複数のMVP⾏行行列列を頂点シェーダに渡せるようにする
⁃ カスタム頂点データセットを準備
⁃ 頂点毎にMVP⾏行行列列を選択できるようにする
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
//!
Custom
Vertex
Data
struct
V3F_C4B_T2F_I1
{
//!
verWces
(3F)
ccVertex3F
verWces;
//
12
bytes
//!
colors
(4B)
ccColor4B
colors;
//
4
bytes
//!
tex
coords
(2F)
ccTex2F
texCoords;
//
8
bytes
//!
matrix
index
float
matIdx;
//
4
bytes
};
53. 異異なるノード階層にある描画をまとめる
! 複数のMVP⾏行行列列を頂点シェーダに渡せるようにする
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
⁃ カスタム頂点シェーダを準備
uniform
mat4
CC_MVPMatrixArray[30];
aUribute
vec4
a_posiWon;
//
V3F_C4B_T2F_I1::verWcesに対応する
aUribute
vec2
a_texCoord;
//
V3F_C4B_T2F_I1::texCoordsに対応する
aUribute
vec4
a_color;
//
V3F_C4B_T2F_I1::colorsに対応する
aUribute
float
a_index;
//
V3F_C4B_T2F_I1::matIdxに対応する
arying
vec4
v_fragmentColor;
varying
vec2
v_texCoord;
void
main()
{
gl_PosiWon
=
CC_MVPMatrixArray[int(a_index)]
*
a_posiWon;
v_fragmentColor
=
a_color;
v_texCoord
=
a_texCoord;
}
54. 異異なるノード階層にある描画をまとめる
! カスタム頂点シェーダ
⁃ CCSpriteだけでなく、CCSpriteBatchNode
も1つのドローコールにまとめた
• FFRKでは、CCScale9Spriteが多⽤用されている
! 独⾃自のバッチノードを作ったわけではない
⁃ バッチレンダラー(シングルトン)に対して、
CCSpriteやCCSpriteBatchNodeが⾃自⾝身で頂点
データを登録するようにする
⁃ バッチレンダラーがバッチ描画要件をチェック
し、要件を満たさないものが登録されると描画
キックする
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
55. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
バッチ描画の流流れ
! CCSprite::draw
⁃ バッチレンダラーでCCSpriteを描画
• バッチ描画要件を満たすかチェック
⁃ 満たしていなかったら、フラッシュする
(登録済みの頂点データを使ってバッチ描画)
• 頂点データとMVP⾏行行列列、テクスチャなどのパラメータをバッチレンダラー
に登録
! CCSpriteBatchNode::draw
⁃ バッチレンダラーでCCSpriteBatchNodeを描画
• バッチ描画要件を満たすかチェック
⁃ 満たしていなかったら、フラッシュする
(登録済みの頂点データを使ってバッチ描画)
• 全⼦子ノードのCCSpriteの頂点データを登録する
• MVP⾏行行列列、テクスチャなどのパラメータをバッチレンダラーに登録
56. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
最終結果
ドローコール数:319
Before
ドローコール数:76
!
Aier
58. Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
最終結果
Before
Aier
実は、基準フレームレートを30FPSに落落としている。
60FPS必要なゲーム性ではない+バッテリー消費を
抑える⽅方を優先した⽅方が良良い、という判断。
59. まとめ
! FFRKのネイティブ層(cocos2d-‐‑‒x)をアレコレやって最適化しました
Copyright
(C)
DeNA
Co.,Ltd.
All
Rights
Reserved.
⁃ 今回の最適化は正味5営業⽇日くらい(+QA期間)
! ちゃんとプロファイラを使ってボトルネックを洗いだせれば、
処理理負荷を改善可能
! ただし、処理理負荷を減らすための⼿手法は完全にアイデア勝負
⁃ エンジニアの腕次第
! プロファイル結果とソースを⾒見見ながら、うんうん唸って考え続ける
! とにかく考える
! そして、神が降降りてくるのを待つ!
! ⾒見見事、神が降降臨臨しました
(∩´́∀`)∩