research.tri

モバイルGPUでのハイエンド
レンダリングエンジン開発事例
(株)トライエース 研究開発部
永野和博 大嶋貴史 Elliott Davis
コンテンツ
• 開発を始める前に
• 実装
• 最適化手法
コンテンツ
• 開発を始める前に
– 達成目標
– 開発環境
– スケーラビリティを意識する
– 具体的な作業箇所
達成目標
• 達成目標
– 自社開発のPS3, XBOX360等スマートフォンよりも
高性能な機器で動作する, マルチプラットフォー
ムエンジンをスマートフォン上でも動作するように
ポーティングを行う
• 対応プラットフォーム
– iOS5.0以上
– Android 2.3以上
達成目標
検証GPUの種類
Vendor
Imagination Technologies
Qualcomm
NVIDIA
ARM
Adreno
Adreno
Adreno
Adreno
Adreno
Tegra 2
Tegra 3
Tegra 4
Mali-400 MP
Mali-T604
GPU
PoweVR SGX 540 (200MHz)
PoweVR SGX 540 (384MHz)
PoweVR SGX 535
PoweVR SGX 543MP2
PoweVR SGX 543MP4
PoweVR SGX 554MP4
iOS Device
205
220
225
320
330
Android
上記のGPUの種類だけではなく, 更にCPU/OSの組み合わせが
あるため対応すべきデバイスの種類は数え切れず, これからも
増え続けていく
達成目標
検証GPUの種類
Vendor
Imagination Technologies
Qualcomm
NVIDIA
ARM
Adreno
Adreno
Adreno
Adreno
Adreno
Tegra 2
Tegra 3
Tegra 4
Mali-400 MP
Mali-T604
GPU
PoweVR SGX 540 (200MHz)
PoweVR SGX 540 (384MHz)
PoweVR SGX 535
PoweVR SGX 543MP2
PoweVR SGX 543MP4
PoweVR SGX 554MP4
iOS Device
205
220
225
320
330
Android
上記のGPUの種類だけではなく, 更にCPU/OSの組み合わせが
あるため対応すべきデバイスの種類は数え切れず, これからも
増え続けていく
今後の事を見据えたしっかりとした事前準備はとても大
切
コンテンツ
• 開発を始める前に
– 達成目標
– 開発環境
– スケーラビリティを意識する
– 具体的な作業箇所
開発環境
• 何を用いて開発するか?
IDE
iOS
Android
Xcode
言語
Objective C++
Eclipse C++ (一部Java)
グラフィックスAPI
その他
OpenGL ES 2.0
OpenGL ES 2.0 Native Activity
開発環境
• 何を用いて開発するか?
IDE
iOS
Android
Xcode
言語
Objective C++
Eclipse C++ (一部Java)
グラフィックスAPI
OpenGL ES 2.0
OpenGL ES 2.0 Native Activity
• Xcode上で問題なくデバッグできる
• 端末内のCPU/GPUの比較的統一されている
• こちらの方が開発は容易と予想
その他
開発環境
• 何を用いて開発するか?
IDE
iOS
Android
Xcode
グラフィックスAPI
言語
Objective C++
Eclipse C++ (一部Java)
OpenGL ES 2.0
OpenGL ES 2.0 Native Activity
• Eclipse上でデバッグが困難
– ブレイクポイントに止まらないことがある
– C++を用いない箇所ではブレイクに止まる
• CPU/GPUのバリエーションが多すぎる
• こちらの開発はかなり大変
その他
開発環境
• 開発スタイル
– iOSの開発を先行して行いAndroidに移植
– iOS側で主要な実装と不具合を除去
– Android側は端末毎の固有の問題解決に注力
開発環境
• 現在のEGLとGLES2の状態を出力する関数を作る
– 特にAndroidに有用
• 妥当性:
– glIsOOOO()
– glValidateProgram()
– glCheckFramebufferStatus()
• エラー:
– glGetError()
– eglGetError()
• エラーを出力する際は, 現在のスレッド情報も含め
る
– ID又は名前
– GLES2のコンテキストID
コンテンツ
• 開発を始める前に
– 達成目標
– 開発環境
– スケーラビリティを意識する
– 具体的な作業箇所
スケーラビリティを意識する
• どれくらいの性能差があるのだろう?
– 新製品発売のスパンが短い
• 市場はローエンド機とハイエンド機が混在している
– ローエンドとハイエンドの性能差は10倍~15倍程
度はある
スケーラビリティを意識する
• どれくらいの性能差があるのだろう?
– 新製品発売のスパンが短い
• 市場はローエンド機とハイエンド機が混在している
– ローエンドとハイエンドの性能差は10倍~15倍程
度はある
ローエンド機に合わせてエンジンを作成
スケーラビリティを意識する
• どれくらいの性能差があるのだろう?
– 新製品発売のスパンが短い
• 市場はローエンド機とハイエンド機が混在している
– ローエンドとハイエンドの性能差は10倍~15倍程
度はある
ローエンド機に合わせてエンジンを作成
可能な限り端末毎の最大性能を出させて良い映像を出
す
スケーラビリティを意識する
• 方法
– 端末の性能毎にポストプロセスの設定を調整す
る
• ブルームの解像度
スケーラビリティを意識する
• 方法
– 端末の性能毎にポストプロセスの設定を調整す
る
• ブルームの解像度
– 端末の性能毎にシェーダの複雑さを調整する
• ライト個数
• ライティングをフラグメントシェーダからバーテックス
シェーダへ
• など
スケーラビリティを意識する
• 方法
– 端末の性能毎にポストプロセスの設定を調整す
る
• ブルームの解像度
– 端末の性能毎にシェーダの複雑さを調整する
• ライト個数
• ライティングをフラグメントシェーダからバーテックス
シェーダへ
• など
– 端末の性能毎にランタイム内にてフロントバッファ
, バックバッファ解像度を調整できるようにする
スケーラビリティを意識する
• どのようにして性能値を導くか?
– あらかじめ主要な端末を調査し, ある程度テーブ
ル化しておきそこから外れるものは端末のデバイ
ス情報とテーブルを参考に計算で求める
– 初期化時にベンチマークを走らせて取得する
スケーラビリティを意識する
• パフォーマンス変動をもたらす特殊な要因
– 温度変化
• 端末の温度上昇に連動してCPU/GPUのクロックが
調整されている
– 節電モード
• リフレッシュレートが半分に抑制される
– BlueTooth
• 原因は不明だがOFFにすることによりパフォーマンス
が劇的に改善するケースがあった
コンテンツ
• 開発を始める前に
– 達成目標
– 開発環境
– スケーラビリティを意識する
– 具体的な作業箇所
具体的な作業箇所
• 当初の予想
– ネイティブAPIにかかわる箇所とシェーダに関する
修正を行う程度で問題ない
– iOSからAndroidへの移植もそれほど困難ではな
い
具体的な作業箇所
• 当初の予想
– ネイティブAPIにかかわる箇所とシェーダに関する
修正を行う程度で問題ない
– iOSからAndroidへの移植もそれほど困難ではな
い
当初の予想に反し数々の問題が発生
具体的な作業箇所
• エンジン内部の動作
メインスレッド
ワーカースレッド1
N+2
N+1
N+2
N+3
…
N+1
…
ワーカースレッドN
レンダースレッド
N+3
N+2
N+1
N
N+3
N+1
N+2
VSync(垂直同期)
VSync(垂直同期)
具体的な作業箇所
メインスレッ
ド
PrepareForRendering
ワーカースレッド
レンダースレッド
RenderContext生成
(Shader, Texture, State, etc)
…
Task処理
DrawCall
Dynamics, Collision, Animation
その他
PreRender
Matrix, Culling
その他レンダリング準備
WaitDraw
Flush
VSync
Swap
具体的な作業箇所
• 変更が必要だった箇所
– 画面更新のタイミング
端末毎に異なっている事が判明
具体的な作業箇所
• 変更が必要だった箇所
– 画面更新のタイミング
端末毎に異なっている事が判明
– マルチスレッドレンダリング
レンダリングコンテキストの問題
具体的な作業箇所
• 変更が必要だった箇所
– 画面更新のタイミング
端末毎に異なっている事が判明
– マルチスレッドレンダリング
レンダリングコンテキストの問題
– シェーダ関連
GPUに依存する問題
具体的な作業箇所
• 変更が必要だった箇所
– 画面更新のタイミング
端末毎に異なっている事が判明
– マルチスレッドレンダリング
レンダリングコンテキストの問題
– シェーダ関連
GPUに依存する問題
– レンダーターゲット関連
GPU, 端末種に依存する問題
具体的な作業箇所
• 変更が必要だった箇所
– 画面更新のタイミング
端末毎に異なっている事が判
明
– マルチスレッドレンダリング
レンダリングコンテキストの問題
– シェーダ関連
GPUに依存する問題
– レンダーターゲット関連
GPU, 端末種に依存する問題
– 最適化
GPU毎に適した方法
実装
コンテンツ
• 実装
– リフレッシュレート
– マルチスレッドレンダリング
– GPU毎のUniforms
– フレームバッファ
リフレッシュレート
• 画面更新のタイミング
– VSync(垂直同期)のシグナルを利用
リフレッシュレート
• 画面更新のタイミング
– VSync(垂直同期)のシグナルを利用
ここで問題発生!!
リフレッシュレート
• 問題の概要
– Vsync(垂直同期)のシグナルが取得できない
• Android 4.0以下のみの問題
• iOSデバイスでは問題ない
– Androidではリフレッシュレートが端末毎に変化
– アニメーション等アセットが基準フレームレートに
基づいて作成されている場合, 再生速度が変化
して
しまう
リフレッシュレート
1秒間に画面の更新が何回行われるかの事で単位はHz
リフレッシュレート
• リフレッシュレートを確認
– JNI(Java Native Interface)を用いて端末に問い
合わせる
– 自分で計測する
リフレッシュレート
• リフレッシュレートを確認
– JNI(Java Native Interface)を用いて端末に問い
合わせる
– 自分で計測する
C++側
JNIEnv*
pEnv
= GetJNIEnv();
Android_app* pApp
= GetAndroidApp();
jmethodID
ID
= pEnv->GetMethodID("GetRefreshRate", "()F");
float fRefreshRate
=
(float)pEnv->CallFloatMethod(pApp()->activity->clazz, mID);
Java側
public float GetRefreshRate(){
WindowManager windowManager = getWindowManager();
Display display = windowManager.getDefaultDisplay();
return display.getRefreshRate();
}
リフレッシュレート
• リフレッシュレートを確認
– JNI(Java Native Interface)を用いて端末に問い
合わせる
– 自分で計測する
uPrevTime = getCurrentTime();
for( int i = 0; i < COUNT; i++ ) {
glClear( GL_DEPTH_BUFFER_BIT|
GL_COLOR_BUFFER_BIT|
GL_STENCIL_BUFFER_BIT );
eglSwapBuffers(EGLData.display,EGLData.surface);
}
uAfterTime = getCurrentTime();
uTotal = AfterTime – uPrevTime;
sRefreshRate = uTotal / COUNT;
リフレッシュレート
• 取得結果
GalaxyS2
GalaxyS
(SC-02B)
GalaxyS2
(ISW11SC)
Arrowsμ
(F-07D)
ArrowsZ
(ISW13F)
MediasLTE
(N-04D)
問い合わせ値
68Hz
60.382Hz
55Hz
60Hz
60Hz
60.382Hz
計測値
53Hz
59Hz
57Hz
61Hz
59Hz
30Hz
計測値と問い合わせた値が一致しな
い
(ISW11SC)
節電モード
以前の実装
• 基準フレームレートをもとにした可変フレーム
レート方式
– アプリケーション起動時, 基準フレームレートを設
定
– フレームレートは基準フレームレートの整数分の
1で可変
• 基準フレームレートが30の時
– 30, 15, 10, 7.5…の範囲で可変
リフレッシュレート
• 解決策
– 完全可変フレームレート方式
• 1フレームにかかった時間を計測し, その時間を用い
てアニメーションを再生
– フレームドロップ方式
• 1フレーム時間と基準フレーム時間の差分を蓄積し,
差分が基準フレーム分蓄積した際にフレームドロップ
を行う
リフレッシュレート
• 完全可変フレームレート方式
– 長所
• アニメーションが滑らか
– 短所
• アセットが可変フレームレートを想定していない場合
想定どおりの再生ができない
• フレームドロップ方式
– 長所
• アセットが基準フレームレートに基づいて作成されて
いても想定どおりの時間で再生可能
– 短所
• アニメーションが滑らかではない
コンテンツ
• 実装
– リフレッシュレート
– マルチスレッドレンダリング
– GPU毎のUniforms
– フレームバッファ
マルチスレッドレンダリング
• 概要
1. 複数のGLES2のコンテキスト
2. パフォーマンス調査
EGLの仕様
• 複数のコンテキストが作成可能
• 各スレッド間でどのコンテキストも使用可能
• 作成したリソースは共有可能
– テクスチャ
– レンダーバッファ
– シェーダ
– バーテックスバッファ
– インデックスバッファ
–等
一般の実装
• コンテキストが必要な
スレッド
レンダー
シェーダコンパイル
ゲームオブジェクト更新
iOSでの最初の実装
• コンテキストが必要な
スレッド
レンダー
シェーダコンパイル
ゲームオブジェクト更新
アプリメイン
• 複数のコンテキストを
使用
iOSでの最初の実装
• コンテキストが必要な
スレッド
レンダー
シェーダコンパイル
ゲームオブジェクト更新
アプリメイン
• 複数のコンテキストを
使用
• EAGLContextの
renderbufferStor
age()を呼ぶタイミン
グ問題の影響でアプリ
メインスレッドで呼ぶ
Androidの実装
• コンテキストが必要な
スレッド
レンダー
シェーダコンパイル
ゲームオブジェクト更新
• iOSと同様に複数のコン
テキストを使おうとした
が
• いくつかのAndroid端
末はコンテキストを
一つしか使えない!
• このままではマルチス
レッドレンダリング不可
能
Androidの実装
• スレッド:
使えない
レンダー
シェーダコンパイル
ゲームオブジェクト更新
• いくつかのAndroid端
末はコンテキストを
一つしか使えない!
• このままではマルチス
レッドレンダリング不可
能
Androidの実装 ー 解決方法
• スレッド:
使えない
レンダー
シェーダコンパイル
ゲームオブジェクト更新
Androidの実装 ー 解決方法
• スレッド:
コンテキストが必要
使えない
レンダー
シェーダコンパイル
ゲームオブジェクト更新
1. コンテキストをレンダ
ースレッドにバインド
する
Androidの実装 ー 解決方法
• スレッド:
コンテキストが必要
使えない
1. コンテキストをレンダ
ースレッドにバインド
する
レンダー
レンダー
シェーダコンパイル
シェーダコンパイル
ゲームオブジェクト更新
2. シェーダコンパイルを
レンダースレッドで行
う
Androidの実装 ー 解決方法
• スレッド:
コンテキストが必要
使える
使えない
1. コンテキストをレンダ
ースレッドにバインド
する
レンダー
シェーダコンパイル
ゲームオブジェクト更新
2. シェーダコンパイルを
レンダースレッドで行
う
3. GLES2リソースのラッ
パークラスの実装を変
更
Androidの実装 ー 解決方法
• スレッド:
コンテキストが必要
使える
使えない
3. GLES2リソースのラッ
パークラスの実装を変
更
レンダー
シェーダコンパイル
•
ゲームオブジェクト更新
リソースを作成や変更
の場合
– GLES2関数を呼ばない
– その為のデータを保持
Androidの実装 ー 解決方法
• スレッド:
コンテキストが必要
使える
使えない
3. GLES2リソースのラッ
パークラスの実装を変
更
レンダー
シェーダコンパイル
ゲームオブジェクト更新
•
GLES2リソースを使う
前に
– 保持したデータを
GLES2関数に渡して、
リソースを作成や更新
•
必要な時だけ
最終結果
Android
コンテキストが必要
レンダー
シェーダコンパイル
ゲームオブジェクト更新
iOS
使える
使えない
レンダー
シェーダコンパイル
ゲームオブジェクト更新
アプリメイン
マルチスレッドレンダリング
• 概要
1. 複数のGLES2のコンテキスト
2. パフォーマンス調査
パフォーマンス
• CPU処理が重いシーンで測定
– 最大パーティクル数を高くする
– 放出率を高くする
– パーティクルデータの更新:
• ワーカースレッド
で行う
• レンダースレッドで
行わない
パフォーマンス結果
fps
50
43.5
40
34
30
20
20
18
10
0
fps
X peria SO -02C
(シングルコア)
M ED IA S N-04D
(マルチコア)
iPhone4
(シングルコア)
レンダースレッド無し レンダースレッド有り
iPhone4S
(マルチコア)
パフォーマンス結果
fps
50
マルチコアで高速化に成功
43.5
40
34
30
20
20
18
10
0
fps
X peria SO -02C
(シングルコア)
M ED IA S N-04D
(マルチコア)
iPhone4
(シングルコア)
レンダースレッド無し レンダースレッド有り
iPhone4S
(マルチコア)
パフォーマンス結果
fps
50
シングルコアでも速度向上に成功
• GLES2のAPI内で発生するブロッキング中にメ
34
インスレッドへスイッチしたため, その分速度
を向上させることができている模様
40
30
20
43.5
20
18
10
0
fps
X peria SO -02C
(シングルコア)
M ED IA S N-04D
(マルチコア)
iPhone4
(シングルコア)
レンダースレッド無し レンダースレッド有り
iPhone4S
(マルチコア)
パフォーマンス結果
fps
50
40
シングルコアでは速度が多少減少
43.5
• スレッド切り替えコストが高く、
34
速度が上がらない
30
20
20
18
10
0
fps
X peria SO -02C
(シングルコア)
M ED IA S N-04D
(マルチコア)
iPhone4
(シングルコア)
レンダースレッド無し レンダースレッド有り
iPhone4S
(マルチコア)
パフォーマンス結果
fps
50
40
30
20
シングルコアでは端末によってメリット又
は
34
デメリットがある
43.5
• 解決方法:ランタイム中に端末毎にレンダー
スレッドを使用するかどうかを決定
20
18
10
0
fps
X peria SO -02C
(シングルコア)
M ED IA S N-04D
(マルチコア)
iPhone4
(シングルコア)
レンダースレッド無し レンダースレッド有り
iPhone4S
(マルチコア)
パフォーマンス
• ドローコール数が多いシーンで測定
– 複数のボックスを配置
– 先程と同じくCPU負荷を意図的に高くしておく
パフォーマンス結果
fps
50
40
35.5
28
30
20
19
15
10
0
fps
Xperia SO-02C
(シングルコア)
MEDIAS N-04D
(マルチコア)
レンダースレッド無し
iPhone4
(シングルコア)
レンダースレッド有り
iPhone4S
(マルチコア)
コンテンツ
• 実装
– リフレッシュレート
– マルチスレッドレンダリング
– GPU毎のUniforms
– フレームバッファ
Uniformとは?
• シェーダで利用される定数データ
• HLSLの場合、シェーダコンスタントと呼ばれる
Uniformとは?
• glGetActiveUniform()で情報が取得
– 名前
– 配列サイズ
–型
ここで問題発生
Uniformとは?
• glGetActiveUniform()で情報が取得
– 名前
– 配列サイズ
–型
ここで問題発生
問題の概要
• glGetActiveUniform()で情報が取得
– 名前
– 配列サイズ
–型
GLES2の仕様によると
GPUによって変化しない
問題の概要
• glGetActiveUniform()で情報が取得
– 名前
– 配列サイズ
–型
実際には変化してしまう
実例1
• シェーダ内に構造体を作成
• 構造体内に配列型のメンバがある
• それをuniformデータとして用いた時
struct mat3x4
{
vec4 highp m[3];
};
uniform mat3x4 cmWorld;
実例1
• シェーダ内に構造体を作成
• 構造体内に配列型のメンバがある
• それをuniformデータとして用いた時
struct mat3x4
{
vec4 highp m[3];
};
uniform mat3x4 cmWorld;
実例1
• シェーダ内に構造体を作成
• 構造体内に配列型のメンバがある
• それをuniformとして用いた時
struct mat3x4
{
vec4 highp m[3];
};
uniform mat3x4 cmWorld;
実例1の取得結果
struct mat3x4
{
vec4 highp m[3];
};
uniform mat3x4 cmWorld;
iO S端末/ A dreno2 0 5 / T egra3
名前
配列サイズ
cmWorld.m[0]
3
A dreno2 2 0 / 2 2 5 / 3 2 0
名前
配列サイズ
cmWorld.m[0]
3
cmWorld.m[1]
2
cmWorld.m[2]
1
実例1の取得結果
struct mat3x4
{
vec4 highp m[3];
};
uniform mat3x4 cmWorld;
iO S端末/ A dreno2 0 5 / T egra3
名前
配列サイズ
cmWorld.m[0]
3
A dreno2 2 0 / 2 2 5 / 3 2 0
名前
配列サイズ
cmWorld.m[0]
3
cmWorld.m[1]
2
cmWorld.m[2]
1
GLES2の仕様と一致
名前1つに、配列サイズ=3
実例1の取得結果
struct mat3x4
{
vec4 highp m[3];
};
uniform mat3x4 cmWorld;
iO S端末/ A dreno2 0 5 / T egra3
名前
配列サイズ
cmWorld.m[0]
3
A dreno2 2 0 / 2 2 5 / 3 2 0
名前
配列サイズ
cmWorld.m[0]
3
cmWorld.m[1]
2
cmWorld.m[2]
1
特殊
名前3つに、それぞれにデクリメントされた配列サイズ
配列サイズとは
struct mat3x4
{
vec4 highp m[3];
};
uniform mat3x4 cmWorld;
iO S端末/ A dreno2 0 5 / T egra3
名前
配列サイズ
cmWorld.m[0]
3
A dreno2 2 0 / 2 2 5 / 3 2 0
名前
配列サイズ
cmWorld.m[0]
3
cmWorld.m[1]
2
cmWorld.m[2]
1
• 取得した配列サイズはいつも
– X 変数の宣言と同じ
– O シェーダ内に使用している要素だけ
配列サイズとは
struct mat3x4
{
vec4 highp m[3];
};
uniform mat3x4 cmWorld;
iO S端末/ A dreno2 0 5 / T egra3
A dreno2 2 0 / 2 2 5 / 3 2 0
名前
使用中の配列サイズ
名前
使用中の配列サイズ
cmWorld.m[0]
1~3
cmWorld.m[0]
1~3
cmWorld.m[1]
1~2
cmWorld.m[2]
1
配列サイズ → 使用中の配列サイズ
実例2
• テクスチャサンプラ配列のみ
• 構造体と無関係
uniform vec4
uniform sampler2D
vCoef[4]
asTexStage[16];
実例2の取得結果
• テクスチャサンプラ配列のみ
• 構造体と無関係
uniform vec4
uniform sampler2D
vCoef[4]
asTexStage[16];
iOS端末/Adreno 205/Tegra 3
Adreno 220/225/320
名前
使用中の配列サイズ
名前
使用中の配列サイズ
vCoef[0]
1~4*
vCoef[0]
1~4*
asTexStage[0]
1~16*
asTexStage[0]
1
asTexStage[1]
1
…
asTexStage[15]**
1
実例2の取得結果
• テクスチャサンプラ配列のみ
• 構造体と無関係
uniform vec4
uniform sampler2D
vCoef[4]
asTexStage[16];
iOS端末/Adreno 205/Tegra 3
Adreno 220/225/320
名前
使用中の配列サイズ
名前
使用中の配列サイズ
vCoef[0]
1~4*
vCoef[0]
1~4*
asTexStage[0]
1~16*
asTexStage[0]
1
asTexStage[1]
1
…
asTexStage[15]**
1
GLES2の仕様と一致
* 名前1つにつき使用中の配列サイズが一致
実例2の取得結果
• テクスチャサンプラ配列のみ
• 構造体と無関係
uniform vec4
uniform sampler2D
vCoef[4]
asTexStage[16];
iOS端末/Adreno 205/Tegra 3
Adreno 220/225/320
名前
使用中の配列サイズ
名前
使用中の配列サイズ
vCoef[0]
1~4*
vCoef[0]
1~4*
asTexStage[0]
1~16*
asTexStage[0]
1
asTexStage[1]
1
…
asTexStage[15]**
1
特殊
**
名前の数は実際に使用されているsampler2D数になり、
全ての配列サイズが1になる
実例3
• 配列を使う
• 型と無関係
• 構造体と無関係
uniform
uniform
uniform
uniform
vec4
sampler2D
int
bool
vCoef[4]
asTexStage[16];
aInteger[16];
aBool[16];
実例3の取得結果
• 配列を使う
• 型と無関係
• 構造体と無関係
iOS端末/Adreno 205/Tegra 3
名前
使用中の配列サイズ
vCoef[0]
1~4*
asTexStage[0]
1~16*
aInteger[0]
1~16*
aBool[0]
1~16*
uniform
uniform
uniform
uniform
vec4
sampler2D
int
bool
vCoef[4]
asTexStage[16];
aInteger[16];
aBool[16];
Mali-400 MP
名前
使用中の配列サイズ
vCoef[0]
4
asTexStage[0]
16
aInteger[0]
16
aBool[0]
16
特殊
シェーダ内で全ての要素を使用してなくても
配列の最大要素数が返ってくる
解決方法
• Uniform処理に合わせて
– シェーダ内のuniform宣言を変更
• 構造体を利用しない
• テクスチャサンプラの配列を利用しない
– ランタイムで取得したuniform情報の違いを
考慮
解決方法
• Uniform処理に合わせて
– シェーダ内のuniform宣言を変更
• 構造体を利用しない
• テクスチャサンプラの配列を利用しない
– ランタイムで取得したuniform情報の違いを
考慮
コンテンツ
• 実装
– リフレッシュレート
– マルチスレッドレンダリング
– GPU毎のUniforms
– フレームバッファ
フレームバッファ
• 概要
– フロントバッファのスワップの最適化
– レンダターゲットの最適化
スワップの内部動作
1. フロントバッファのカラーを端末の画面に表示
2. EGLの設定によりスワップ後のフロントバッファの
カラーの挙動が変化
•
•
保持する
保持しない
)
(変わらない)
(壊れているか変えられている可能性あり
上記の設定はデフォルトがGPUとOSに依存
スワップの内部動作
1. フロントバッファのカラーを端末の画面に表示
2. EGLの設定によりスワップ後のフロントバッファの
カラーの挙動が変化
•
•
保持する
保持しない
)
(変わらない)
(壊れているか変えられている可能性あり
上記の設定はデフォルト値が
GPUとOSに依存
スワップの内部動作
• カラーバッファを保持しない方が速い
– 多くのアプリは画面全体をフレーム毎に描画するので
カラーバッファを保存しなくても良い
• デフォルト設定が「保持」のGPUを「保持しない」よう
に変更すれば最適化が測れる
• 概要
– 設定のデフォルト値と変更仕方の紹介
– パフォーマンスの結果
スワップの内部動作
• カラーバッファを保持しない方が速い
– 多くのアプリは画面全体をフレーム毎に描画するので
カラーバッファを保存しなくても良い
• デフォルト設定が「保持」のGPUを「保持しない」よう
に変更すれば最適化が図れる
• 概要
– 設定のデフォルト値と変更仕方の紹介
– パフォーマンスの結果
iOSの場合
• kEAGLDrawablePropertyRetainedBacking
• YES = 保持する
• NO = 保持しない
• デフォルト値 = NO
Androidの場合 (EGL1.4以上)
• eglSurfaceAttrib( EGL_SWAP_BEHAVIOR, EGL_BUFFER_*** )
– EGL_BUFFER_DESTROYED = 保持しない
– EGL_BUFFER_PRESERVED = 保持する
• デフォルト値はGPU毎に異なる
• EGL_SWAP_BEHAVIORが設定できないことがある
– EGL 1.4以上が必要
• 公式にはNDK r5以上でサポート
– EGL仕様によるとEGL Surfaceのコンフィギュレーションを作
る時にEGL_SWAP_BEHAVIOR_PRESERVED_BITが必要
Androidの場合 (EGL1.4以上)
• eglSurfaceAttrib( EGL_SWAP_BEHAVIOR, EGL_BUFFER_*** )
– EGL_BUFFER_DESTROYED = 保持しない
– EGL_BUFFER_PRESERVED = 保持する
• デフォルト値はGPU毎に異なる
端末名
端末ID
GALAXY S
AQUOS PHONE
ARROWS μ
Xperia
GALAXY S II WiMAX
GALAXY S II LTE
ARROWS Z
SC-02B
SH06-D
F-07D
SO-02E
ISW11SC
SC-03D
ISW13F
GPU
EGL_SWAP_BEHAVIOR
デフォルト値
Power VR SGX 540 200MHz
EGL_BUFFER_PRESERVED
Power VR SGX 540 384MHz
Adreno 205
Adreno 320
EGL_BUFFER_DESTROYED
Mali-400 MP
Adreno 220
NVIDIA Tegra 3
Androidの場合 (EGL1.4以上)
• EGL_SWAP_BEHAVIORが設定できないことがある
– EGL 1.4以上が必要
• 公式にはNDK r5以上でサポート
– OS 2.3と同時に出た
– EGL仕様によるとEGL Surfaceのコンフィギュレーションに
EGL_SWAP_BEHAVIOR_PRESERVED_BITが必要
• ビットフラッグ設定の意味は
– 設定している =
– 設定しない =
EGL_SWAP_BEHAVIORが変更可能
EGL_SWAP_BEHAVIORが変更不可能
Androidの場合 (EGL1.4以上)
• EGL_SWAP_BEHAVIORが設定できないことがある
– EGL 1.4以上が必要
• 公式にはNDK r5以上でサポート
– OS 2.3と同時に出た
– EGL仕様によるとEGL Surfaceのコンフィギュレーションに
EGL_SWAP_BEHAVIOR_PRESERVED_BITが必要
• ビットフラグ設定の意味は
– 設定している
– 設定しない
=
=
EGL_SWAP_BEHAVIORが変更可能
EGL_SWAP_BEHAVIORが変更不可能
Androidの場合 (EGL1.4以上)
EGL_SWAP_BEHAVIOR_PRESERVED_BITに
対応しているEGL Surfaceコンフィギュレーションを
eglGetConfigs()から取得可能
端末名
端末ID
GPU
GALAXY S
AQUOS PHONE
ARROWS μ
Xperia
GALAXY S II WiMAX
GALAXY S II LTE
ARROWS Z
SC-02B
SH06-D
F-07D
SO-02E
ISW11SC
SC-03D
ISW13F
Power VR SGX 540 200MHz
Power VR SGX 540 384MHz
Adreno 205
Adreno 320
Mali-400 MP
Adreno 220
NVIDIA Tegra 3
EGL_SWAP_BEHAVIOR_
PRESERVED_BIT
不可能
可能
• PowerVR SGX 540は「不可能」でもEGL_SWAP_BEHAVIORの値が変更できる
– EGLの仕様の例外
Androidの場合 (EGL1.4以上)
EGL_SWAP_BEHAVIOR_PRESERVED_BITに
対応しているEGL Surfaceコンフィギュレーションを
eglGetConfigs()から取得可能
端末名
端末ID
GPU
GALAXY S
AQUOS PHONE
ARROWS μ
Xperia
GALAXY S II WiMAX
GALAXY S II LTE
ARROWS Z
SC-02B
SH06-D
F-07D
SO-02E
ISW11SC
SC-03D
ISW13F
Power VR SGX 540 200MHz
Power VR SGX 540 384MHz
Adreno 205
Adreno 320
Mali-400 MP
Adreno 220
NVIDIA Tegra 3
EGL_SWAP_BEHAVIOR_
PRESERVED_BIT
不可能
可能
• PowerVR SGX 540は「不可能」でもEGL_SWAP_BEHAVIORの値が変更できる
– EGLの仕様の例外
Androidのパフォーマンス結果
fps
• 効果が見られず
44
41
38
35
fps
G alaxy S (Pow er V R SG X 5 4 0 )
E G L_B U FFE R _P R E S E R V E D
E G L_B U FFE R _D E S T R O Y E D
フレームバッファ
• 概要
– フロントバッファのスワップの最適化
– レンダターゲットの最適化
レンダターゲットの最適化
• 遅いGPUのため
– フロントバッファの形式
• RGBA8888 → RGB565
• 解像度を下げる
– バックバッファ
– フロントバッファ
• 可能な場合のみ。変更出来ない端末もある
レンダターゲットの最適化
• 遅いGPUのため
– フロントバッファの形式
• RGBA8888 → RGB565
– 解像度を下げる
• バックバッファ
• フロントバッファ
– 可能な場合のみ。変更出来ない端末もある
フロントバッファ解像度変更条件
Androidの場合
• 変更可能
– ただし全てではない
– OSのバージョンに依存
• ANativeWindow_setBuffersGeometory()
またはeglCreateWindowSurface()を利用
フロントバッファ解像度変更条件
iOSの場合
• 変更可能
– ただし全てではない
– “Retina ディスプレイ”に限られる
– 解像度指定はできずScalingのみ可能
• UIView::contentScaleFactorを利用
バックバッファ解像度
• GPUによって
– 初期化時にパフォーマンスを計測
– 事前に計測した値を使用
• テーブルに保持してある
• 結果を利用してディスプレイ解像度に対して
のバックバッファのスケール係数を決定する
最適化手法
検証したGPU
• Adreno
– 205, 220, 225, 320
• Mali
– 400 MP
• PowerVR (SGX)
– 535, 540, 543, 554
• Tegra
– Tegra 3
コンテンツ
• 最適化手法
– タイルベースGPU最適化
– オブジェクト描画順
– テクスチャフェッチ最適化
タイルベースGPU
• タイルベースレンダリングを採用したGPU
– PowerVR, Adreno, Mali
• タイルベースレンダリング(TBR)
– フレームバッファへのアクセス
• レンダリング中はフレームバッファにアクセスせずGPU
内のタイルバッファにアクセスする
• 各タイルのレンダリング
– 開始時 ⇒ GPUのタイルバッファにロード
– 終了時 ⇒ GPUのタイルバッファからストア
タイルのロード, ストア
• 通常のレンダリングでは描画開始時のロードは不必
要
– 一般的にはレンダリング開始直後にすべてクリア
• OpenGL ES + TBRではデフォルトではロードが発生
• ストア
– レンダリング後のフレームバッファの内容を使用しない場
合
• 例) カラーは必要, デプス, ステンシルは不必要
• ロード, ストアを制御するGL_EXTがある
GL_EXT_discard_framebuffer
• 対応GPU:Mali, PowerVR
– 古いバージョンのAndroid OSではサポートされない
• glDiscardFramebufferEXT()
– 破棄するカラー, デプス, ステンシルを指定
• 破棄された場合dirtyになる
GL_QCOM_tiled_rendering
• 対応GPU:Adreno
• StartTilingQCOM()
EndTilingQCOM()
– 状態を保持するカラー, デプス, ステンシルを指定
• 保持しないものはdirtyになる
– StartTilingQCOM(), EndTilingQCOM()
は対にして使用する必要がある
最適化方法
• ロードの必要がない場合
– PowerVR, Mali
• レンダリング開始時にglClearを使用してクリア
– Adreno
• レンダリング開始時にdirtyにする
– StartTilingQCOM()
• dirtyの場合はロードされない
• ストアの必要がない場合
– レンダリングの最後にタイルバッファをdirtyにする
• glDiscardFramebufferEXT()
EndTilingQCOM()
• dirtyの場合はストアされない
コンテンツ
• 最適化手法
– タイルベースGPU最適化
– オブジェクト描画順
– テクスチャフェッチ最適化
オブジェクト描画順
• ここでのオブジェクトの単位は描画順のソート
を行う単位
– レンダリングエンジンの実装に依存
– 本セッションではドローコール単位でソート
一般的なケース
不透明オブジェクト
パンチスルーオブジェクト
early-zを考慮し, 手前から順に描画
半透明オブジェクト
奥から順に描画
不透明オブジェクト
• early-z cull 調査
– オーバードローテスト
• Zテスト: near
• 1ポリゴン30 fpsになる負荷のフラグメントシェーダを使用
• 手前から描画した場合と、奥から描画した場合で比較
不透明オブジェクト
• early-z cull 調査
– オーバードローテスト
• Zテスト: near
• 1ポリゴン30 fpsになる負荷のフラグメントシェーダを使用
• 手前から描画した場合と、奥から描画した場合で比較
fps
1ポリゴン
手前から6 ポリゴン
奥から6 ポリゴン
35
30
25
20
15
10
5
0
Adreno
Mali
PowerVR
Tegra
不透明オブジェクト描画順
• Adreno, Mali, Tegra
– 手前から順に描画
• オーバードローが発生する可能性がある
– オブジェクトが重なっている
– オブジェクト内のポリゴンがソートされていない
– ポリゴンが交差している
• PowerVR
– 描画順は考慮しなくてよい
• オーバードローは発生しない
– ただし例外あり
» タイル内のポリゴン数が一定を超えると
オーバードローが発生する等
不透明オブジェクト描画順
• Adreno, Mali, Tegra
– 手前から順に描画
• オーバードローは発生する
– オブジェクト同士が重なっている
– オブジェクト内のポリゴンの並びが手前から順になっていな
い
– ポリゴンが交差している
• PowerVR
– 描画順は考慮しなくてよい
• 基本的にオーバードローは発生しない
– タイル内のポリゴン数が一定を超えると
オーバードローが発生するなど、例外あり
パンチスルーオブジェクト
• early-z cull 調査
– オーバードローテスト
• 不透明オブジェクトのテストと同じ条件
• 不透明オブジェクトテスト時のシェーダにdiscard命令を追加
– 破棄されるピクセルは無し
パンチスルーオブジェクト
• early-z cull 調査
– オーバードローテスト
• 不透明オブジェクトのテストと同じ条件
• 不透明オブジェクトテスト時のシェーダにdiscard命令を追加
– 破棄されるピクセルは無し
fps
1ポリゴン
手前から6 ポリゴン
奥から6 ポリゴン
35
30
25
20
15
10
5
0
Adreno
Mali
PowerVR
Tegra
不透明&パンチスルー
• early-z cull 調査
– オーバードローテスト
• 不透明オブジェクト, パンチスルーオブジェクトを交互に6ポリゴン描画
• パンチスルーオブジェクトから描画した場合と、不透明オブジェクトから描
画した場合を比較
不透明&パンチスルー
• early-z cull 調査
– オーバードローテスト
• 不透明オブジェクト, パンチスルーオブジェクトを交互に6ポリゴン描画
• パンチスルーオブジェクトから描画した場合と、不透明オブジェクトから描
画した場合を比較
fps
奥から順
手前から順(不透明から)
手前から順(パンチスルーから)
35
30
25
20
15
10
5
0
Adreno
Mali
PowerVR
Tegra
パンチスルーオブジェクト描画順
• Mali
– 不透明オブジェクトの後に描画
• 描画順は考慮しない
• Adreno, Tegra
– 不透明オブジェクトより先に描画
• 不透明はパンチスルーに陰面消去された場合
オーバードローが発生しないため
• 常にオーバードローが発生する
– 描画順を考慮する必要はない
パンチスルーオブジェクト描画順
• PowerVR
– 不透明とパンチスルーオブジェクトを分けて描画
• 不透明オブジェクトのみの場合はオーバードローが発
生しないため
• 不透明オブジェクトの前後どちらで描画するのかは
不透明オブジェクトとのオーバードロー次第
• 手前から描画
オブジェクト描画順まとめ
Mali
Adreno, Tegra
PowerVR
不透明
パンチスルー
パンチスルー
手前から順に描画
描画順は考慮しない
手前から順に描画
不透明
パンチスルー
不透明
描画順は考慮しない
手前から順に描画
描画順は考慮しない
パンチスルー
手前から順に描画
半透明
半透明
奥から順に描画
奥から順に描画
半透明
奥から順に描画
コンテンツ
• 最適化手法
– タイルベースGPU最適化
– オブジェクト描画順
– テクスチャフェッチ最適化
PowerVRテクスチャプリフェッチ
• プリフェッチ条件
uniform sampler2D s0;
varying vec2 uv0;
varying vec4 uv1;
– UVをvaryingから値を変えずに使用
– varyingのスィズルをxから順に使用 texture2D(s0,
• PowerVR以外でこのフェッチの
有効性をポストプロセスで調査
– ¼ダウンサンプリング
– ガウスフィルタ
uv0); // ○
vec2 uv2 = uv0;
uv2 += vec2(0.1, 0.2);
texture2D(s0, uv2); // ×
texture2D(s0, uv1.xy); // ○
texture2D(s0, uv1.zw); // ×
PowerVRテクスチャプリフェッチ
• プリフェッチ条件
uniform sampler2D s0;
varying vec2 uv0;
varying vec4 uv1;
– UVをvaryingから値を変えずに使用
– varyingのスィズルをxから順に使用 texture2D(s0,
• PowerVR以外でこのフェッチの
有効性をポストプロセスで調査
– ¼ダウンサンプリング
– ガウスフィルタ
uv0); // ○
vec2 uv2 = uv0;
uv2 += vec2(0.1, 0.2);
texture2D(s0, uv2); // ×
texture2D(s0, uv1.xy); // ○
texture2D(s0, uv1.zw); // ×
PowerVRテクスチャプリフェッチ
• プリフェッチ条件
uniform sampler2D s0;
varying vec2 uv0;
varying vec4 uv1;
– UVをvaryingから値を変えずに使用
– varyingのスィズルをxから順に使用 texture2D(s0,
• PowerVR以外でこのフェッチの
有効性をポストプロセスで調査
– ¼ダウンサンプリング
– ガウスフィルタ
uv0); // ○
vec2 uv2 = uv0;
uv2 += vec2(0.1, 0.2);
texture2D(s0, uv2); // ×
texture2D(s0, uv1.xy); // ○
texture2D(s0, uv1.zw); // ×
PowerVRテクスチャプリフェッチ
• プリフェッチ条件
uniform sampler2D s0;
varying vec2 uv0;
varying vec4 uv1;
– UVをvaryingから値を変えずに使用
– varyingのスィズルをxから順に使用 texture2D(s0,
• PowerVR以外でこのフェッチの
性能をポストプロセスで調査
– ¼ダウンサンプリング
– ガウスフィルタ
uv0); // ○
vec2 uv2 = uv0;
uv2 += vec2(0.1, 0.2);
texture2D(s0, uv2); // ×
texture2D(s0, uv1.xy); // ○
texture2D(s0, uv1.zw); // ×
テクスチャフェッチ比較対象
• バーテックスシェーダUV生
成
– varyingの値を変えずに使用
– varying数をできるだけ削減
• フラグメントシェーダUV生成
– varyingは1つ
uniform sampler2D s0;
varying vec4 uv0;
varying vec4 uv1;
texture2D(s0,
texture2D(s0,
texture2D(s0,
texture2D(s0,
uniform
varying
uniform
uniform
Uniform
uv0.xy);
uv0.zw);
uv1.xy);
uv1.zw);
sampler2D s0;
vec2 uv;
vec2 ofs0;
vec2 ofs1;
vec2 ofs2;
texture2D(s0, uv+ofs0);
texture2D(s0, uv+ofs1);
texture2D(s0, uv+ofs2);
テクスチャフェッチ検証環境
• カラーフォーマット: ARGB8888
• Precision
– バーテックスシェーダ: highp
– フラグメントシェーダ: mediump
• パフォーマンス計測方法
– フレームレートから計測
– 60fps以下にするため解像度を調整
1/4ダウンサンプリング
• テクスチャフェッチ4回
1/4ダウンサンプリング結果
Pow erVRプリフェッチ
↑Fast
1.2
バーテックスUV生成
フラグメントUV生成
1
0.8
0.6
0.4
0.2
0
205
220
Adreno
225
320
400 MP
Mali
T604
Tegra 3
540
PowerVR
ガウスフィルタ
• テクスチャフェッチ7回
ガウスフィルタ結果
Pow erVRプリフェッチ
↑Fast
1.2
バーテックスUV生成
フラグメントUV生成
1
0.8
0.6
0.4
0.2
0
205
220
Adreno
225
320
400 MP
Mali
T604
Tegra 3
540
PowerVR
テクスチャフェッチ結果
• Mali-400 MP
– PowerVRプリフェッチが有効
• UV精度も向上する
• Adreno 200シリーズ, Tegra 3
– PowerVRプリフェッチの有効性は見られない
– varying数増加によるパフォーマンス低下は確認できず
• フラグメントシェーダ負荷の少ないバーテックスUV生成推奨
– PowerVRのプリフェッチを使用して問題ない
• Adreno 320, Mali-T604
– varying数削減が効果的
まとめ
• 性能差が大幅に異なるGPUに対応が必要
• OS, GPU, 端末の違いによるさまざまな問題
が発生
– 特にAndroidでは仕様書などのドキュメントが信
用できないことが多い
• 各GPUの最適化手法の調査, 検証が必要
謝辞
• (株)トライエース研究開発部
– 五反田 義治
ご質問は?
http://research.tri-ace.com