狠狠撸

狠狠撸Share a Scribd company logo
FINAL 
FANTASY 
Record 
Keeper 
cocos2d-?‐xレイヤーの最適化 
株式会社ディー?エヌ?エー 
Japanリージョン 
ゲーム事業本部 
技術?編成部 
開発基盤グループ 
惠良良 
和隆 
kazutaka.era@dena.com 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved.
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
?自?己紹介 
! 惠良良和隆(えら 
かずたか) 
? 2002年年 
株式会社フロム?ソフトウェア?入社 
? コンソールゲームの開発(クライアント、サーバー) 
? ライブラリ、フレームワークの開発 
? 開発環境構築 
? etc 
? 2013年年10?月 
株式会社ディー?エヌ?エー?入社 
? ゲームアプリ開発に必要なライブラリやフレームワーク、 
サーバーなどの開発するグループに所属 
? ゲーム開発のワークフロー整備 
? cocos2d-‐??xをベースとした社内ゲームエンジンの開発 
? ゲームシステムのアーキテクチャアドバイザー 
? etc
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
本?日のお題 
FFRKのエンジンであるKickmotorの 
cocos2d-?‐xレイヤーに関する最適化の話
ご注意 
! このスライドの内容は、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.
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
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
なぜ最適化が必要だったのか? 
! CBTの散々たる結果 
? 重い(超もっさり) 
? 熱い 
? 充電速度度 
 
バッテリー消費量量 
! WebViewとcocos2d-‐??xのどちらも重い 
? パーティ編成などはWebView 
? バトルはcocos2d-‐??x 
! 開発時にはiPhone5Sなどのハイスペック端末で確認 
? 低スペック端末でのチェックを怠っていた
パフォチュー祭り開催! 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
\(^o^)/
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¬|?::::::::::::::::::::::::::::::::::::::::::::::::| \
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
cocos2d-?‐xレイヤーの問題とは? 
! cocos2d-‐??x 
? イケてないC++実装 
? cocos2d-‐??xの都合を理理解して実装しないと性能が出ない 
? 凝ったことをやろうとすると性能が出ない 
! 内製アニメーション再?生エンジン 
? ?自由度度を持たせるための汎?用的な実装 
? 汎?用性を上げるとオーバーヘッドも?大きくなる 
? 実?行行効率率率を意識識していない実装 
! アニメーションデータ 
? 表現?力力と?生産性だけを追求したデータ 
? 実?行行効率率率が全く考慮されていない
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
つまり??? 
全く最適化されていない???orz
最適化?方針 
! CBT終了了から正式リリースまで時間的余裕は無い 
! 出来る限りリスクが低いコード修正に絞りたい 
? 複数プランを出し、?手軽で効果の?高そうなものから着?手 
! ?大規模なデータ改修は避けたい 
! ベンチマークとなる端末(iOS、Android)を決める 
? iOS:iPhone4S 
? Android:Galaxy 
S2(Android2.3系のもの) 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
! 最適化作業はiOS端末で?行行う 
? Androidに?比べてプロファイラが充実している 
? ネイティブコードのデバッグのし易易さ
最適化?手順 
1. コンパイラによる最適化が有効なビルドを準備 
2. プロファイラで情報収集(Xcode、Instruments) 
3. ボトルネックの候補を抽出 
4. 負荷原因を調査 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
a) コードを?見見て推測 
b) 推測を元にコード修正 
c) パフォーマンスの変化をチェック 
5. 正式なコード修正 
6. 1.に戻る
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
Xcodeのプロファイル機能
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
Xcodeのプロファイル機能
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
Xcodeのプロファイル機能
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
Xcodeのプロファイル機能
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
Xcodeのプロファイル機能
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
Instruments 
の 
Time 
Profiler
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
Instruments 
の 
Time 
Profiler
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
具体的なボトルネックの例例 
! 頂点数0のドローコール 
! ?見見えないスプライト描画 
! ありえないほど多い描画パス 
! 無駄なOpenGL 
ES 
API呼び出し(DrawCall以外) 
! 透明部分の多いスプライト描画 
! ?大量量のCCNodeで構成されたシーングラフ 
! cocos2d-‐??xのダメ実装 
! 画?面外への描画 
! ?大量量のドローコール数
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
頂点数0のドローコール
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
頂点数0のドローコール
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
頂点数0のドローコール
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
?見見えないスプライト描画
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
?見見えないスプライト描画 
頂点情報を確認してみると、 
RGBAが全て0になっていると分かる
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
?見見えないスプライト描画
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
ありえないほど多い描画パス
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
ありえないほど多い描画パス
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
ありえないほど多い描画パス 
毎フレーム、20回もglClear()をコール!
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
ありえないほど多い描画パス
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
ありえないほど多い描画パス 
諸悪の根源:マスクノード
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
ありえないほど多い描画パス 
! マスクノードとは? 
? ?自?身の?子孫ノードをテクスチャに描画し、 
そのテクスチャに対してαマスクを適?用する独?自拡張ノード 
! 便便利利なので???と多?用された結果、描画パスが20に! 
マスクノード禁?止令令発令令!! 
本当に必要なところでしかマスクノードは使わない。 
バトル画?面で使われているものは、 
マスクノードでなくても良良いものだった。 
これらを削ることで、描画パスは1になった。
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
無駄なOpenGL 
ES 
API呼び出し(DrawCall以外)
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
無駄なOpenGL 
ES 
API呼び出し(DrawCall以外) 
問題となっている箇所は全て独?自拡張したコードで、 
cocos2d-?‐xのOpenGLステートキャッシュを使っていない 
? 
ステートキャッシュを使うように修正
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
透明部分の多いスプライト描画
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
透明部分の多いスプライト描画
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
透明部分の多いスプライト描画
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
透明部分の多いスプライト描画 
予め合成した画像を?用意
?大量量のCCNodeで構成されたシーングラフ 
CCNode::visit()が負荷の上位にいる 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
? 
CCNodeが2000程存在していることが理理由
CCNode::visit()の処理理内容 
! Visibleフラグをチェックし、フラグが?立立っていなければ 
何もしない(?子ノードのvisit()も呼ばない) 
! OpenGL?用?行行列列スタック操作 
! アフィン変換?行行列列からOpenGL?用?行行列列の更更新 
! ?子ノードのvisit()呼び出し 
! ?自?身のdraw()呼び出し 
1つ1つは?小さな処理理でも、 
実?行行回数が多いので無視出来ないほどの負荷になる 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved.
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
CCNode::visit()の何を削ったか? 
! 描画すべきノードが配下に無ければ何もしない 
? 画?面上の座標を指定する?目的で、多くのCCNodeがレイアウトされ 
ている(実際に描画ノードを配下に持たない) 
? シーングラフを構築する際に、配下のノードにCCSpriteなどの描 
画されるノードが存在しているかを記録しておく 
? CCNode::onEnter()/CCNode::onExit()を活?用 
? 描画されるノードが配下に存在する場合のみ通常処理理 
! アフィン変換?行行列列が単位?行行列列ならば?行行列列スタック操作をしない 
? グルーピングのためだけにCCNodeを使うことは多い 
? 多くのノードでアフィン変換による座標変化が無い 
? 単位?行行列列は掛けても結果が同じなのでそもそも掛けない 
? ?行行列列が変化しないのでスタック操作もしない
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
cocos2d-?‐xのダメ実装 
! メモリコピーのコストを考えていない! 
? サイズの?大きな構造体を関数の戻り値で返す 
? サイズの?大きな構造体を関数に値渡しする 
! 関数呼び出しのコストを考えていない! 
? 無駄に仮想関数定義されているのでインライン展開されない 
? MFCかよ!?とツッコミたくなるクラス階層 
! double精度度の算術関数の使?用 
? cos()/sin()などを多?用しているがcosf()/sinf()で?十分な場合が多い 
? 計算コストが全然違う! 
! 割り算の回数を意識識していない 
! D$のことを意識識していない 
? 連続アクセスするデータは、連続したメモリ領領域に配置すべき 
? cocoa互換なクラス群がイケてない
画?面外への描画 
! 表?示範囲外に配置されているスプライトやパーティクルを描画しない 
? バウンディングボックスを計算して、 
完全に画?面外にあるものは描画しない 
? パーティクルはエミッタ単位でバッチ描画しているので、 
エミッタ単位のバウンディングボックスを求める 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved.
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
画?面外への描画 
! バウンディングボックスの計算負荷も無視できない 
? 1つのエミッタから放出されるパーティクルは数百?~数千になる 
? FFRKのスクロールの仕様を考慮し、X軸?方向だけに限定してバウ 
ンディングボックスの計算を?行行うようにした 
スクロール?方向
画?面外への描画 
! ワールド座標系への変換?行行列列(画?面内にあるかを判定するために必要) 
を求める必要がある 
? ただし、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; 
}
画?面外への描画 
! ワールド座標系への変換?行行列列(画?面内にあるかを判定するために必要) 
を効率率率的に求める必要がある 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
? ノード座標系からワールド座標系への変換?行行列列を 
?行行列列の乗算1回で求める 
? 階層が深くなっても計算量量が増加しない
?大量量のドローコール数 
! 弊社内製ツールの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) 
???
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
ドローコール数を減らす?方法 
! CCSpriteBatchNodeは利利便便性が低い 
? CCSpriteBatchNode直下にCCSpriteを並べる必要がある 
? データ構造に強い制限を掛けてしまう 
? デザイナーにバッチ描画を意識識させるのは 
ナンセンス 
! バトルシーンのドローコール数は、画?面外描画を 
除去した後でも150程度度ある 
? 100以下にしないと低スペック端末で重い
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
どうやってドローコールを削るか? 
! 結論論 
? ドローコールをまとめて全体の数を減らす 
(バッチ描画) 
? ただし、CCSpriteBatchNode以外の?方法で! 
???という訳で、 
独?自のバッチ描画機能を実装しました!
バッチ描画の要件 
! 以下の条件が揃えば、バッチ描画できる 
? 参照テクスチャが同じ 
? αブレンド設定が同じ 
? シザリング設定が同じ 
? シェーダ(頂点、フラグメント)が同じ 
! この条件は、OpenGL 
ESの仕様によるもの 
! 描画処理理の途中で変更更出来ないパラメータを揃える 
必要がある 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved.
異異なるノード階層にある描画をまとめる 
! ノード階層が異異なる 
→ワールド座標系へのアフィン変換?行行列列が異異なる 
→頂点シェーダに渡す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; 
}
異異なるノード階層にある描画をまとめる 
! 複数の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 
};
異異なるノード階層にある描画をまとめる 
! 複数の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; 
}
異異なるノード階層にある描画をまとめる 
! カスタム頂点シェーダ 
? CCSpriteだけでなく、CCSpriteBatchNode 
も1つのドローコールにまとめた 
? FFRKでは、CCScale9Spriteが多?用されている 
! 独?自のバッチノードを作ったわけではない 
? バッチレンダラー(シングルトン)に対して、 
CCSpriteやCCSpriteBatchNodeが?自?身で頂点 
データを登録するようにする 
? バッチレンダラーがバッチ描画要件をチェック 
し、要件を満たさないものが登録されると描画 
キックする 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved.
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
バッチ描画の流流れ 
! CCSprite::draw 
? バッチレンダラーでCCSpriteを描画 
? バッチ描画要件を満たすかチェック 
? 満たしていなかったら、フラッシュする 
(登録済みの頂点データを使ってバッチ描画) 
? 頂点データとMVP?行行列列、テクスチャなどのパラメータをバッチレンダラー 
に登録 
! CCSpriteBatchNode::draw 
? バッチレンダラーでCCSpriteBatchNodeを描画 
? バッチ描画要件を満たすかチェック 
? 満たしていなかったら、フラッシュする 
(登録済みの頂点データを使ってバッチ描画) 
? 全?子ノードのCCSpriteの頂点データを登録する 
? MVP?行行列列、テクスチャなどのパラメータをバッチレンダラーに登録
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
最終結果 
ドローコール数:319 
Before 
ドローコール数:76 
! 
Aier
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
最終結果 
Before 
Aier
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
最終結果 
Before 
Aier 
実は、基準フレームレートを30FPSに落落としている。 
60FPS必要なゲーム性ではない+バッテリー消費を 
抑える?方を優先した?方が良良い、という判断。
まとめ 
! FFRKのネイティブ層(cocos2d-‐??x)をアレコレやって最適化しました 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved. 
? 今回の最適化は正味5営業?日くらい(+QA期間) 
! ちゃんとプロファイラを使ってボトルネックを洗いだせれば、 
処理理負荷を改善可能 
! ただし、処理理負荷を減らすための?手法は完全にアイデア勝負 
? エンジニアの腕次第 
! プロファイル結果とソースを?見見ながら、うんうん唸って考え続ける 
! とにかく考える 
! そして、神が降降りてくるのを待つ! 
! ?見見事、神が降降臨臨しました 
(∩???`)∩
ご清聴ありがとうございました 
Copyright 
(C) 
DeNA 
Co.,Ltd. 
All 
Rights 
Reserved.

More Related Content

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などのハイスペック端末で確認 ? 低スペック端末でのチェックを怠っていた
  • 7. パフォチュー祭り開催! Copyright (C) DeNA Co.,Ltd. All Rights Reserved. \(^o^)/
  • 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の都合を理理解して実装しないと性能が出ない ? 凝ったことをやろうとすると性能が出ない ! 内製アニメーション再?生エンジン ? ?自由度度を持たせるための汎?用的な実装 ? 汎?用性を上げるとオーバーヘッドも?大きくなる ? 実?行行効率率率を意識識していない実装 ! アニメーションデータ ? 表現?力力と?生産性だけを追求したデータ ? 実?行行効率率率が全く考慮されていない
  • 10. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. つまり??? 全く最適化されていない???orz
  • 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.に戻る
  • 13. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. Xcodeのプロファイル機能
  • 14. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. Xcodeのプロファイル機能
  • 15. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. Xcodeのプロファイル機能
  • 16. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. Xcodeのプロファイル機能
  • 17. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. Xcodeのプロファイル機能
  • 18. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. Instruments の Time Profiler
  • 19. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. Instruments の Time Profiler
  • 20. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 具体的なボトルネックの例例 ! 頂点数0のドローコール ! ?見見えないスプライト描画 ! ありえないほど多い描画パス ! 無駄なOpenGL ES API呼び出し(DrawCall以外) ! 透明部分の多いスプライト描画 ! ?大量量のCCNodeで構成されたシーングラフ ! cocos2d-‐??xのダメ実装 ! 画?面外への描画 ! ?大量量のドローコール数
  • 21. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 頂点数0のドローコール
  • 22. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 頂点数0のドローコール
  • 23. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 頂点数0のドローコール
  • 24. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ?見見えないスプライト描画
  • 25. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ?見見えないスプライト描画 頂点情報を確認してみると、 RGBAが全て0になっていると分かる
  • 26. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ?見見えないスプライト描画
  • 27. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ありえないほど多い描画パス
  • 28. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ありえないほど多い描画パス
  • 29. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ありえないほど多い描画パス 毎フレーム、20回もglClear()をコール!
  • 30. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ありえないほど多い描画パス
  • 31. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ありえないほど多い描画パス 諸悪の根源:マスクノード
  • 32. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ありえないほど多い描画パス ! マスクノードとは? ? ?自?身の?子孫ノードをテクスチャに描画し、 そのテクスチャに対してαマスクを適?用する独?自拡張ノード ! 便便利利なので???と多?用された結果、描画パスが20に! マスクノード禁?止令令発令令!! 本当に必要なところでしかマスクノードは使わない。 バトル画?面で使われているものは、 マスクノードでなくても良良いものだった。 これらを削ることで、描画パスは1になった。
  • 33. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 無駄なOpenGL ES API呼び出し(DrawCall以外)
  • 34. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 無駄なOpenGL ES API呼び出し(DrawCall以外) 問題となっている箇所は全て独?自拡張したコードで、 cocos2d-?‐xのOpenGLステートキャッシュを使っていない ? ステートキャッシュを使うように修正
  • 35. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 透明部分の多いスプライト描画
  • 36. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 透明部分の多いスプライト描画
  • 37. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 透明部分の多いスプライト描画
  • 38. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 透明部分の多いスプライト描画 予め合成した画像を?用意
  • 39. ?大量量のCCNodeで構成されたシーングラフ CCNode::visit()が負荷の上位にいる Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ? CCNodeが2000程存在していることが理理由
  • 40. CCNode::visit()の処理理内容 ! Visibleフラグをチェックし、フラグが?立立っていなければ 何もしない(?子ノードのvisit()も呼ばない) ! OpenGL?用?行行列列スタック操作 ! アフィン変換?行行列列からOpenGL?用?行行列列の更更新 ! ?子ノードのvisit()呼び出し ! ?自?身のdraw()呼び出し 1つ1つは?小さな処理理でも、 実?行行回数が多いので無視出来ないほどの負荷になる Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
  • 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互換なクラス群がイケてない
  • 43. 画?面外への描画 ! 表?示範囲外に配置されているスプライトやパーティクルを描画しない ? バウンディングボックスを計算して、 完全に画?面外にあるものは描画しない ? パーティクルはエミッタ単位でバッチ描画しているので、 エミッタ単位のバウンディングボックスを求める Copyright (C) DeNA Co.,Ltd. All Rights Reserved.
  • 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; }
  • 46. 画?面外への描画 ! ワールド座標系への変換?行行列列(画?面内にあるかを判定するために必要) を効率率率的に求める必要がある Copyright (C) DeNA Co.,Ltd. All Rights Reserved. ? ノード座標系からワールド座標系への変換?行行列列を ?行行列列の乗算1回で求める ? 階層が深くなっても計算量量が増加しない
  • 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
  • 57. Copyright (C) DeNA Co.,Ltd. All Rights Reserved. 最終結果 Before 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期間) ! ちゃんとプロファイラを使ってボトルネックを洗いだせれば、 処理理負荷を改善可能 ! ただし、処理理負荷を減らすための?手法は完全にアイデア勝負 ? エンジニアの腕次第 ! プロファイル結果とソースを?見見ながら、うんうん唸って考え続ける ! とにかく考える ! そして、神が降降りてくるのを待つ! ! ?見見事、神が降降臨臨しました (∩???`)∩
  • 60. ご清聴ありがとうございました Copyright (C) DeNA Co.,Ltd. All Rights Reserved.