モバイル GPU でのハイエンド レンダリングエンジン開発事例

Preview:

DESCRIPTION

モバイル GPU でのハイエンド レンダリングエンジン開発事例. (株)トライエース 研究開発部 永野和博 大嶋貴史  Elliott Davis. コンテンツ. 開発を始める前に 実装 最適化手法. 開発を始める前に 達成目標 開発環境 スケーラビリティを意識する 具体的な作業箇所. コンテンツ. 達成目標. 達成目標 自社開発の PS3, XBOX360 等スマートフォンよりも高性能な機器で動作する , マルチプラットフォームエンジンをスマートフォン上でも動作するようにポーティングを行う 対応プラットフォーム iOS5.0 以上 - PowerPoint PPT Presentation

Citation preview

モバイル GPU でのハイエンドレンダリングエンジン開発事例

(株)トライエース 研究開発部永野和博 大嶋貴史  Elliott Davis

コンテンツ

• 開発を始める前に• 実装• 最適化手法

コンテンツ

• 開発を始める前に– 達成目標 – 開発環境– スケーラビリティを意識する– 具体的な作業箇所

達成目標

• 達成目標– 自社開発の PS3, XBOX360 等スマートフォ

ンよりも高性能な機器で動作する , マルチプラットフォームエンジンをスマートフォン上でも動作するようにポーティングを行う

• 対応プラットフォーム– iOS5.0 以上 – Android 2.3 以上

達成目標

検証 GPU の種類Vendor Imagination Technologies Qualcomm NVIDIA ARM

PoweVR SGX 540 (200MHz) Adreno 205 Tegra 2 Mali-400 MPPoweVR SGX 540 (384MHz) Adreno 220 Tegra 3 Mali-T604

PoweVR SGX 535 Adreno 225 Tegra 4PoweVR SGX 543MP2 Adreno 320PoweVR SGX 543MP4 Adreno 330PoweVR SGX 554MP4

GPU

上記の GPU の種類だけではなく , 更に CPU/OS の組み合わせがあるため対応すべきデバイスの種類は数え切れず , これからも増え続けていく

iOS Device

Android

達成目標

検証 GPU の種類

上記の GPU の種類だけではなく , 更に CPU/OS の組み合わせがあるため対応すべきデバイスの種類は数え切れず , これからも増え続けていく

iOS Device

Android

今後の事を見据えたしっかりとした事前準備はとても大切

Vendor Imagination Technologies Qualcomm NVIDIA ARMPoweVR SGX 540 (200MHz) Adreno 205 Tegra 2 Mali-400 MPPoweVR SGX 540 (384MHz) Adreno 220 Tegra 3 Mali-T604

PoweVR SGX 535 Adreno 225 Tegra 4PoweVR SGX 543MP2 Adreno 320PoweVR SGX 543MP4 Adreno 330PoweVR SGX 554MP4

GPU

コンテンツ

• 開発を始める前に– 達成目標 – 開発環境– スケーラビリティを意識する– 具体的な作業箇所

IDE 言語 グラフィックスAPI その他

iOS Xcode Objective C++ OpenGL ES 2.0Android Eclipse C++ (一部J ava) OpenGL ES 2.0 Native Activity

開発環境

• 何を用いて開発するか?

IDE 言語 グラフィックスAPI その他

iOS Xcode Objective C++ OpenGL ES 2.0Android Eclipse C++ (一部J ava) OpenGL ES 2.0 Native Activity

開発環境

• Xcode 上で問題なくデバッグできる

• 端末内の CPU/GPU の比較的統一されている

• こちらの方が開発は容易と予想

• 何を用いて開発するか?

IDE 言語 グラフィックスAPI その他

iOS Xcode Objective C++ OpenGL ES 2.0Android Eclipse C++ (一部J ava) 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 への移植もそれほど困難で

はない

当初の予想に反し数々の問題が発生

具体的な作業箇所

具体的な作業箇所

N+1 N+2

N

N+1 N+2

N+1

VSync (垂直同期)

N+3

VSync (垂直同期)

VSync (垂直同期)

N+2

N+3メインスレッド

ワーカースレッド 1

レンダースレッド

… …N+1 N+2 N+3ワーカースレッ

ド N

• エンジン内部の動作

具体的な作業箇所

PrepareForRendering RenderContext生成

(Shader, Texture, State, etc)

Task処理

PreRender

WaitDraw

VSync

Dynamics, Collision, Animation

その他

Matrix, Culling

その他レンダリング準備

… DrawCall

Flush

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) を用いて端末に

問い合わせる– 自分で計測する

public float GetRefreshRate(){WindowManager windowManager = getWindowManager();Display display = windowManager.getDefaultDisplay();return display.getRefreshRate();

}

JNIEnv* pEnv = GetJNIEnv();Android_app* pApp = GetAndroidApp();jmethodID ID = pEnv->GetMethodID("GetRefreshRate", "()F");float fRefreshRate =(float)pEnv->CallFloatMethod(pApp()->activity->clazz, mID);

リフレッシュレート

C++ 側

Java 側

リフレッシュレート

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;

• リフレッシュレートを確認– JNI(Java Native Interface) を用いて端末に

問い合わせる– 自分で計測する

リフレッシュレート

問い合わせ値 68Hz 60.382Hz 55Hz 60Hz 60Hz 60.382Hz

計測値 53Hz 59Hz 57Hz 61Hz 59Hz 30Hz

GalaxyS2(ISW11SC)節電モード

GalaxyS(SC-02B)

Arrowsμ(F-07D)

MediasLTE(N-04D)

GalaxyS2(ISW11SC)

ArrowsZ(ISW13F)

計測値と問い合わせた値が一致しない

• 取得結果

以前の実装

• 基準フレームレートをもとにした可変フレームレート方式–アプリケーション起動時 , 基準フレームレー

トを設定– フレームレートは基準フレームレートの整数分の1で可変

• 基準フレームレートが 30 の時– 30, 15, 10, 7.5… の範囲で可変

リフレッシュレート

• 解決策–完全可変フレームレート方式

• 1フレームにかかった時間を計測し , その時間を用いてアニメーションを再生

– フレームドロップ方式• 1フレーム時間と基準フレーム時間の差分を蓄積

し , 差分が基準フレーム分蓄積した際にフレームドロップを行う

リフレッシュレート

• 完全可変フレームレート方式–長所

• アニメーションが滑らか– 短所

• アセットが可変フレームレートを想定していない場合想定どおりの再生ができない

• フレームドロップ方式–長所

• アセットが基準フレームレートに基づいて作成されていても想定どおりの時間で再生可能

– 短所• アニメーションが滑らかではない

コンテンツ

• 実装– リフレッシュレート– マルチスレッドレンダリング– GPU 毎の Uniforms– フレームバッファ

マルチスレッドレンダリング

• 概要1.複数の GLES2 のコンテキスト2.パフォーマンス調査

EGL の仕様

• 複数のコンテキストが作成可能• 各スレッド間でどのコンテキストも使用可

能• 作成したリソースは共有可能

– テクスチャ– レンダーバッファ– シェーダ– バーテックスバッファ– インデックスバッファ– 等

一般の実装

シェーダコンパイル

レンダー

ゲームオブジェクト更新

• コンテキストが必要なスレッド

iOS での最初の実装• 複数のコンテキスト

を使用

 

• コンテキストが必要なスレッド

シェーダコンパイル

レンダー

ゲームオブジェクト更新

アプリメイン

iOS での最初の実装• 複数のコンテキスト

を使用

• EAGLContext のrenderbufferStorage() を呼ぶタイミング問題の影響でアプリメインスレッドで呼ぶ

 

• コンテキストが必要なスレッド

シェーダコンパイル

レンダー

ゲームオブジェクト更新

アプリメイン

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処理が重いシーンで測定– 最大パーティクル数を高くする–放出率を高くする– パーティクルデータの更新:

• ワーカースレッドで行う

• レンダースレッドで行わない

パフォーマンス

20

34

18

43.5

0

10

20

30

40

50

Xperia SO- 02C( )シングルコア

MEDIAS N- 04D( )マルチコア

iPhone4( )シングルコア

iPhone4S( )マルチコア

レンダースレッド無し レンダースレッド有り

パフォーマンス結果

fps

fps

20

34

18

43.5

0

10

20

30

40

50

Xperia SO- 02C( )シングルコア

MEDIAS N- 04D( )マルチコア

iPhone4( )シングルコア

iPhone4S( )マルチコア

レンダースレッド無し レンダースレッド有り

パフォーマンス結果

マルチコアで高速化に成功fps

fps

20

34

18

43.5

0

10

20

30

40

50

Xperia SO- 02C( )シングルコア

MEDIAS N- 04D( )マルチコア

iPhone4( )シングルコア

iPhone4S( )マルチコア

レンダースレッド無し レンダースレッド有り

パフォーマンス結果

シングルコアでも速度向上に成功• GLES2 の API 内で発生するブロッキング中にメインスレッドへスイッチしたため , その分速度を向上させることができている模様

fps

fps

20

34

18

43.5

0

10

20

30

40

50

Xperia SO- 02C( )シングルコア

MEDIAS N- 04D( )マルチコア

iPhone4( )シングルコア

iPhone4S( )マルチコア

レンダースレッド無し レンダースレッド有り

パフォーマンス結果

シングルコアでは速度が多少減少• スレッド切り替えコストが高く、速度が上がらない

fps

fps

20

34

18

43.5

0

10

20

30

40

50

Xperia SO- 02C( )シングルコア

MEDIAS N- 04D( )マルチコア

iPhone4( )シングルコア

iPhone4S( )マルチコア

レンダースレッド無し レンダースレッド有り

パフォーマンス結果

シングルコアでは端末によってメリット又は

デメリットがある• 解決方法:ランタイム中に端末毎にレン

ダースレッドを使用するかどうかを決定

fps

fps

• ドローコール数が多いシーンで測定– 複数のボックスを配置– 先程と同じく CPU負荷を意図的に高くして

おく

パフォーマンス

19

28

15

35.5

0

10

20

30

40

50

Xperia SO- 02C(シングルコア)

MEDIAS N- 04D(マルチコア)

iPhone4(シングルコア)

iPhone4S(マルチコア)

レンダースレッド無し レンダースレッド有り

パフォーマンス結果fps

fps

コンテンツ

• 実装– リフレッシュレート– マルチスレッドレンダリング– GPU 毎の Uniforms– フレームバッファ

Uniform とは ?• シェーダで利用される定数データ

• HLSL の場合、シェーダコンスタントと呼ばれる

• glGetActiveUniform ()で情報が取得– 名前–配列サイズ–型

ここで問題発生

Uniform とは ?

• glGetActiveUniform ()で情報が取得– 名前–配列サイズ–型

ここで問題発生

Uniform とは ?

• glGetActiveUniform ()で情報が取得– 名前–配列サイズ–型

GLES2 の仕様によるとGPU によって変化しない

問題の概要

• glGetActiveUniform ()で情報が取得– 名前–配列サイズ–型

実際には変化してしまう

問題の概要

• シェーダ内に構造体を作成• 構造体内に配列型のメンバがある• それを 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;

• シェーダ内に構造体を作成• 構造体内に配列型のメンバがある• それを uniform として用いた時

実例1

名前 配列サイズ 名前 配列サイズcmWorld.m[0] 3 cmWorld.m[0] 3

cmWorld.m[1] 2cmWorld.m[2] 1

iOS / Adreno205/ Tegra3端末 Adreno220/ 225/ 320

struct mat3x4{ vec4 highp m[3]; };  uniform mat3x4 cmWorld;

実例1の取得結果

GLES2 の仕様と一致

名前1つに、配列サイズ=3

名前 配列サイズ 名前 配列サイズcmWorld.m[0] 3 cmWorld.m[0] 3

cmWorld.m[1] 2cmWorld.m[2] 1

iOS / Adreno205/ Tegra3端末 Adreno220/ 225/ 320

実例1の取得結果

struct mat3x4{ vec4 highp m[3]; };  uniform mat3x4 cmWorld;

特殊

名前3つに、それぞれにデクリメントされた配列サイズ

特殊

名前 配列サイズ 名前 配列サイズcmWorld.m[0] 3 cmWorld.m[0] 3

cmWorld.m[1] 2cmWorld.m[2] 1

iOS / Adreno205/ Tegra3端末 Adreno220/ 225/ 320

実例1の取得結果

struct mat3x4{ vec4 highp m[3]; };  uniform mat3x4 cmWorld;

名前 配列サイズ 名前 配列サイズcmWorld.m[0] 3 cmWorld.m[0] 3

cmWorld.m[1] 2cmWorld.m[2] 1

iOS / Adreno205/ Tegra3端末 Adreno220/ 225/ 320

配列サイズとは

struct mat3x4{ vec4 highp m[3]; };  uniform mat3x4 cmWorld;

• 取得した配列サイズはいつも– X 変数の宣言と同じ– O シェーダ内に使用している要素だけ

名前 使用中の配列サイズ 名前 使用中の配列サイズcmWorld.m[0] 1~3 cmWorld.m[0] 1~3

cmWorld.m[1] 1~2cmWorld.m[2] 1

iOS / Adreno205/ Tegra3端末 Adreno220/ 225/ 320

配列サイズ → 使用中の配列サイズ

struct mat3x4{ vec4 highp m[3]; };  uniform mat3x4 cmWorld;

配列サイズとは

実例2

• テクスチャサンプラ配列のみ• 構造体と無関係

uniform vec4 vCoef[4]uniform sampler2D asTexStage[16];

名前 使用中の配列サイズ 名前 使用中の配列サイズvCoef[0] 1~4* vCoef[0] 1~4*asTexStage[0] 1~16* asTexStage[0] 1

asTexStage[1] 1…asTexStage[15]** 1

iOS端末/Adreno 205/Tegra 3 Adreno 220/225/320

• テクスチャサンプラ配列のみ• 構造体と無関係

実例2の取得結果

• テクスチャサンプラ配列のみ• 構造体と無関係

uniform vec4 vCoef[4]uniform sampler2D asTexStage[16];

• テクスチャサンプラ配列のみ• 構造体と無関係

名前 使用中の配列サイズ 名前 使用中の配列サイズvCoef[0] 1~4* vCoef[0] 1~4*asTexStage[0] 1~16* asTexStage[0] 1

asTexStage[1] 1…asTexStage[15]** 1

iOS端末/Adreno 205/Tegra 3 Adreno 220/225/320

* 名前1つにつき使用中の配列サイズが一致GLES2 の仕様と一致

実例2の取得結果

• テクスチャサンプラ配列のみ• 構造体と無関係

uniform vec4 vCoef[4]uniform sampler2D asTexStage[16];

名前 使用中の配列サイズ 名前 使用中の配列サイズvCoef[0] 1~4* vCoef[0] 1~4*asTexStage[0] 1~16* asTexStage[0] 1

asTexStage[1] 1…asTexStage[15]** 1

iOS端末/Adreno 205/Tegra 3 Adreno 220/225/320

** 名前の数は実際に使用されている sampler2D 数になり、

全ての配列サイズが1になる

特殊

実例2の取得結果

• テクスチャサンプラ配列のみ• 構造体と無関係

uniform vec4 vCoef[4]uniform sampler2D asTexStage[16];

実例3

uniform vec4 vCoef[4]uniform sampler2D asTexStage[16];uniform int aInteger[16];uniform bool aBool[16];

• 配列を使う• 型と無関係• 構造体と無関係

名前 使用中の配列サイズ 名前 使用中の配列サイズvCoef[0] 1~4* vCoef[0] 4asTexStage[0] 1~16* asTexStage[0] 16aInteger[0] 1~16* aInteger[0] 16aBool[0] 1~16* aBool[0] 16

iOS端末/Adreno 205/Tegra 3 Mali-400 MP

シェーダ内で全ての要素を使用してなくても配列の最大要素数が返ってくる

• 配列を使う• 型と無関係• 構造体と無関係

特殊

実例3の取得結果

uniform vec4 vCoef[4]uniform sampler2D asTexStage[16];uniform int aInteger[16];uniform bool aBool[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 が必要

• eglSurfaceAttrib( EGL_SWAP_BEHAVIOR, EGL_BUFFER_*** )– EGL_BUFFER_DESTROYED  = 保持しない– EGL_BUFFER_PRESERVED  = 保持する

• デフォルト値は GPU 毎に異なる

端末名 端末ID GPU EGL_SWAP_BEHAVIORデフォルト値

GALAXY S SC-02B Power VR SGX 540 200MHzAQUOS PHONE SH06-D Power VR SGX 540 384MHzARROWS μ F-07D Adreno 205Xperia SO-02E Adreno 320GALAXY S II WiMAX ISW11SC Mali-400 MPGALAXY S II LTE SC-03D Adreno 220ARROWS Z ISW13F NVIDIA Tegra 3

EGL_BUFFER_DESTROYED

EGL_BUFFER_PRESERVED

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 以上 )

• PowerVR SGX 540 は「不可能」でも EGL_SWAP_BEHAVIOR の値が変更できる– EGL の仕様の例外

EGL_SWAP_BEHAVIOR_PRESERVED_BIT に対応している EGL Surface コンフィギュレー

ションを eglGetConfigs ()から取得可能

端末名 端末ID GPU EGL_SWAP_BEHAVIOR_PRESERVED_BIT

GALAXY S SC-02B Power VR SGX 540 200MHzAQUOS PHONE SH06-D Power VR SGX 540 384MHzARROWS μ F-07D Adreno 205Xperia SO-02E Adreno 320GALAXY S II WiMAX ISW11SC Mali-400 MPGALAXY S II LTE SC-03D Adreno 220ARROWS Z ISW13F NVIDIA Tegra 3

可能

不可能

Android の場合  (EGL1.4 以上 )

• PowerVR SGX 540 は「不可能」でも EGL_SWAP_BEHAVIOR の値が変更できる– EGL の仕様の例外

EGL_SWAP_BEHAVIOR_PRESERVED_BIT に対応している EGL Surface コンフィギュレー

ションを eglGetConfigs ()から取得可能

端末名 端末ID GPU EGL_SWAP_BEHAVIOR_PRESERVED_BIT

GALAXY S SC-02B Power VR SGX 540 200MHzAQUOS PHONE SH06-D Power VR SGX 540 384MHzARROWS μ F-07D Adreno 205Xperia SO-02E Adreno 320GALAXY S II WiMAX ISW11SC Mali-400 MPGALAXY S II LTE SC-03D Adreno 220ARROWS Z ISW13F NVIDIA Tegra 3

可能

不可能

Android の場合  (EGL1.4 以上 )

Android のパフォーマンス結果

35

38

41

44

Galaxy S (Power VR SGX 540)

EGL_BUFFER_PRESERVEDEGL_BUFFER_DESTROYED

• 効果が見られず

fps

fps

フレームバッファ

• 概要– フロントバッファのスワップの最適化– レンダターゲットの最適化

レンダターゲットの最適化

• 遅い 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 になる負荷のフラグメントシェーダを使

用• 手前から描画した場合と、奥から描画した場合で比較

不透明オブジェクト

05

101520253035

Adreno Mali PowerVR Tegra

fps 1ポリゴン 6手前から ポリゴン 6奥から ポリゴン

• early-z cull 調査– オーバードローテスト

• Z テスト : near • 1ポリゴン 30 fps になる負荷のフラグメントシェーダを使

用• 手前から描画した場合と、奥から描画した場合で比較

不透明オブジェクト描画順

• Adreno, Mali, Tegra– 手前から順に描画

• オーバードローが発生する可能性がある– オブジェクトが重なっている– オブジェクト内のポリゴンがソートされていない– ポリゴンが交差している

• PowerVR–描画順は考慮しなくてよい

• オーバードローは発生しない– ただし例外あり

» タイル内のポリゴン数が一定を超えるとオーバードローが発生する等

不透明オブジェクト描画順

• Adreno, Mali, Tegra– 手前から順に描画

• オーバードローは発生する– オブジェクト同士が重なっている– オブジェクト内のポリゴンの並びが手前から順になっ

ていない– ポリゴンが交差している

• PowerVR–描画順は考慮しなくてよい

• 基本的にオーバードローは発生しない– タイル内のポリゴン数が一定を超えるとオーバードローが発生するなど、例外あり

パンチスルーオブジェクト• early-z cull 調査

– オーバードローテスト• 不透明オブジェクトのテストと同じ条件• 不透明オブジェクトテスト時のシェーダに discard命令を追加

– 破棄されるピクセルは無し

パンチスルーオブジェクト

05

101520253035

Adreno Mali PowerVR Tegra

fps 1ポリゴン 6手前から ポリゴン 6奥から ポリゴン

• early-z cull 調査– オーバードローテスト

• 不透明オブジェクトのテストと同じ条件• 不透明オブジェクトテスト時のシェーダに discard命令を追加

– 破棄されるピクセルは無し

不透明&パンチスルー• early-z cull 調査

– オーバードローテスト• 不透明オブジェクト , パンチスルーオブジェクトを交互に6ポリゴ

ン描画• パンチスルーオブジェクトから描画した場合と、不透明オブジェク

トから描画した場合を比較

不透明&パンチスルー

05

101520253035

Adreno Mali PowerVR Tegra

fps 奥から順 (手前から順 不透明から) (手前から順 パンチスルーから)

• early-z cull 調査– オーバードローテスト

• 不透明オブジェクト , パンチスルーオブジェクトを交互に6ポリゴン描画

• パンチスルーオブジェクトから描画した場合と、不透明オブジェクトから描画した場合を比較

パンチスルーオブジェクト描画順• Mali

– 不透明オブジェクトの後に描画• 描画順は考慮しない

• Adreno, Tegra– 不透明オブジェクトより先に描画

• 不透明はパンチスルーに陰面消去された場合オーバードローが発生しないため

• 常にオーバードローが発生する– 描画順を考慮する必要はない

パンチスルーオブジェクト描画順• PowerVR

– 不透明とパンチスルーオブジェクトを分けて描画

• 不透明オブジェクトのみの場合はオーバードローが発生しないため

• 不透明オブジェクトの前後どちらで描画するのかは不透明オブジェクトとのオーバードロー次第

• 手前から描画

Adreno, Tegra

オブジェクト描画順まとめ

Maliパンチスルー手前から順に描画

PowerVR

半透明奥から順に描画

不透明描画順は考慮しない

パンチスルー手前から順に描画

パンチスルー描画順は考慮しない

半透明奥から順に描画

不透明手前から順に描画

パンチスルー描画順は考慮しない

半透明奥から順に描画

不透明手前から順に描画

コンテンツ

• 最適化手法– タイルベース GPU 最適化–オブジェクト描画順– テクスチャフェッチ最適化

PowerVR テクスチャプリフェッチ• プリフェッチ条件

– UV を varying から値を変えずに使用

– varying のスィズルを x から順に使用

• PowerVR 以外でこのフェッチの有効性をポストプロセスで調査– ¼ ダウンサンプリング– ガウスフィルタ

uniform sampler2D s0;varying vec2 uv0;varying vec4 uv1;

texture2D(s0, uv0); // ○

vec2 uv2 = uv0;uv2 += vec2(0.1, 0.2);texture2D(s0, uv2); // ×

texture2D(s0, uv1.xy); // ○

texture2D(s0, uv1.zw); // ×

PowerVR テクスチャプリフェッチ• プリフェッチ条件

– UV を varying から値を変えずに使用

– varying のスィズルを x から順に使用

• PowerVR 以外でこのフェッチの有効性をポストプロセスで調査– ¼ ダウンサンプリング– ガウスフィルタ

uniform sampler2D s0;varying vec2 uv0;varying vec4 uv1;

texture2D(s0, uv0); // ○

vec2 uv2 = uv0;uv2 += vec2(0.1, 0.2);texture2D(s0, uv2); // ×

texture2D(s0, uv1.xy); // ○

texture2D(s0, uv1.zw); // ×

PowerVR テクスチャプリフェッチ• プリフェッチ条件

– UV を varying から値を変えずに使用

– varying のスィズルを x から順に使用

• PowerVR 以外でこのフェッチの有効性をポストプロセスで調査– ¼ ダウンサンプリング– ガウスフィルタ

uniform sampler2D s0;varying vec2 uv0;varying vec4 uv1;

texture2D(s0, uv0); // ○

vec2 uv2 = uv0;uv2 += vec2(0.1, 0.2);texture2D(s0, uv2); // ×

texture2D(s0, uv1.xy); // ○

texture2D(s0, uv1.zw); // ×

PowerVR テクスチャプリフェッチ• プリフェッチ条件

– UV を varying から値を変えずに使用

– varying のスィズルを x から順に使用

• PowerVR 以外でこのフェッチの性能をポストプロセスで調査– ¼ ダウンサンプリング– ガウスフィルタ

uniform sampler2D s0;varying vec2 uv0;varying vec4 uv1;

texture2D(s0, uv0); // ○

vec2 uv2 = uv0;uv2 += vec2(0.1, 0.2);texture2D(s0, uv2); // ×

texture2D(s0, uv1.xy); // ○

texture2D(s0, uv1.zw); // ×

テクスチャフェッチ比較対象

• バーテックスシェーダ UV生成– varying の値を変えずに使

用– varying 数をできるだけ削減

uniform sampler2D s0;varying vec4 uv0;varying vec4 uv1;

texture2D(s0, uv0.xy);texture2D(s0, uv0.zw);texture2D(s0, uv1.xy);texture2D(s0, uv1.zw);

uniform sampler2D s0;varying vec2 uv;uniform vec2 ofs0;uniform vec2 ofs1;Uniform vec2 ofs2;

texture2D(s0, uv+ofs0);texture2D(s0, uv+ofs1);texture2D(s0, uv+ofs2);

• フラグメントシェーダ UV生成– varying は1つ

テクスチャフェッチ検証環境

• カラーフォーマット: ARGB8888• Precision

– バーテックスシェーダ: highp– フラグメントシェーダ: mediump

• パフォーマンス計測方法– フレームレートから計測– 60fps 以下にするため解像度を調整

1/4 ダウンサンプリング• テクスチャフェッチ4回

1/4 ダウンサンプリング結果

0

0.2

0.4

0.6

0.8

1

1.2

205 220 225 320 400 MP T604 540

PowerVRプリフェッチ UVバーテックス 生成 UVフラグメント 生成↑Fast

PowerVR

MaliAdreno Tegra 3

ガウスフィルタ• テクスチャフェッチ7回

ガウスフィルタ結果

0

0.2

0.4

0.6

0.8

1

1.2

205 220 225 320 400 MP T604 540

PowerVRプリフェッチ UVバーテックス 生成 UVフラグメント 生成

PowerVR

MaliAdreno Tegra 3

↑Fast

テクスチャフェッチ結果

• 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

Recommended