システムの基本を表示
一般的なディスプレイ システムでは、通常、CPU、GPU、およびディスプレイの 3 つの部分で構成されます。
CPU はフレーム データを計算し、計算されたデータを GPU に渡します。
GPU はグラフィックス データをレンダリングし、レンダリング後にバッファー (イメージ バッファー) に格納します。
ディスプレイ (画面またはモニター) は、バッファー内のデータを画面に表示する役割を果たします。
ダブルバッファ
画面のリフレッシュ レートは固定されており、たとえば 16.6ms ごとにバッファからデータがフェッチされ、フレームが表示されます。
理想的には、フレーム レートとリフレッシュ レートは一貫している必要があります。つまり、フレームが描画されるたびに、ディスプレイにフレームが表示されます。ただし、CPU/GPU 書き込みデータは制御不能であり、
そのため、バッファ内の一部のデータはまったく表示されずに書き換えられる場合があります、つまり、バッファ内のデータが別のフレームから来る場合があります. 画面が更新されると、この時点ではバッファの状態がわからないため、バッファからグラブする フレームは完全なフレームではなく、1 つの画面のデータが 2 つの異なるフレームから取得され、画面が引き裂かれるように見えます。簡単に言えば、バッファー内のデータは、ディスプレイの表示プロセス中に CPU/GPU によって変更され、画面が裂ける原因になります。
画面のティアリングを解決するには?つまり、ダブル バッファリングを使用します。
画像描画と画面読み込みで同じバッファを使用するため、画面更新時に不完全なフレームが読み込まれる場合があります。
ダブル バッファリングとは、描画とディスプレイに独自のバッファを持たせることです。GPU は常にイメージ データの完成したフレームをバック バッファに書き込みますが、ディスプレイはフレーム バッファを使用します。
画面が更新されると、フレーム バッファーは変更されず、バック バッファーの準備ができたときに交換されます。以下に示すように:
垂直同期
ここで再び問題が発生します: 2 つのバッファーをいつ交換するか?
画面をスキャンした後、デバイスは次のスキャンに入るために最初の行に戻る必要があります. このとき、垂直ブランキング間隔 ( VBI )と呼ばれる時間のギャップがあります。
この時点は、バッファ交換を実行するのに最適な時期です。この時点では画面が更新されていないため、交換プロセス中に画面が裂ける状況を回避できます。
VSync (Vertical Synchronization) とは Vertical Synchronization の略で、VBI 期間に発生する (垂直同期パルス) を使用して、vertical sync pulse
ダブル バッファーが最適なタイミングで交換されるようにします。また、交換はそれぞれのメモリアドレスを参照しており、動作は瞬時に完了すると考えてよい。
Android4.1以前は、画面更新も上記で紹介したダブル。ただし、次のようなフレーム損失の問題は引き続き発生します。
1. Display は 0 フレーム目のデータを表示します このとき、CPU と GPU は 1 フレーム目をレンダリングし、Display が次のフレームを表示する前にレンダリングが完了します。
2. レンダリングがタイムリーであるため、0 番目のフレームの表示が完了した後、つまり最初の VSync の後、キャッシュが交換され、最初のフレームが正常に表示されます。
3. 次に、2 番目のフレームのレンダリングが開始されますが、2 番目の VSync が来るまで開始されません。
4. 2 番目の VSync が到着すると、2 番目のフレームのデータの準備ができていないため、キャッシュは交換されず、最初のフレームが表示されたままになります。この状況を Android 開発チームは「ジャンク」と名付けました。つまり、フレーム ロスが発生します。
5. 2 番目のフレームのデータ準備が完了すると、すぐには表示されず、次の VSync がバッファー交換を実行するのを待ってから表示されます。
そのため、通常、画面には理由もなく最初のフレームがもう一度表示されます。これは、VSync 信号が到着する前に 2 フレーム目の CPU/GPU 計算が完了していないためです。
私たちは知っています、
ダブル バッファの交換は、Vsyn が到着したときにのみ実行されます。交換後、画面はフレーム バッファ内の新しいデータを取得します。このときのバック バッファは、 GPU が実際に次のフレームを準備するために使用できます。データ。
Vsyn が到着したときに CPU/GPU が動作を開始した場合、完全な 16.6ms があり、基本的にジャンクの発生を回避する必要があります (CPU/GPU の計算が 16.6ms を超えない限り)。
では、Vsyn が登場したときに CPU/GPU コンピューティングを実現するにはどうすればよいでしょうか?
表示パフォーマンスを最適化するために、Google は Android 4.1 システムで Android 表示システムをリファクタリングし、Project Butter (butter プロジェクト) を実装しました。
VSync pulse
つまり、システムが.
CPU/GPU はVSYNC
信号に従って同期的にデータを処理し、CPU/GPU がデータを処理するのに完全な 16ms の時間を確保できるようにし、ジャンクを減らします。
ここでまた問題が発生します。インターフェースがより複雑になり、CPU/GPU の処理時間が 16.6ms を超える場合はどうなるでしょうか。この時、ジャンクが再び現れます。
一定時間、GPU がまだ B フレームを処理しているため、バッファをスワップできず、A フレームが繰り返し表示されます。B が完了した後、VSync パルス信号が不足しているため、次の信号が来るのを待つことしかできません。次の VSync が発生すると、CPU/GPU はすぐに操作を実行し (A フレーム)、キャッシュが交換され、対応する表示画面が B に対応します。その時は普通に見えます。ただ、実行時間はまだ16msを超えているため、次に実行すべきバッファ交換がまた先延ばしされるというサイクルが繰り返され、どんどん「ジャンク」が発生していきます。
上記の理由を分析すると、次の根本原因が原因である可能性が高くなります。
バッファは 2 つしかなく、Back バッファは GPU が B フレームのデータを処理するために使用されており、Frame バッファの内容は Display に使用されているため、両方のバッファが占有され、CPU は B フレームのデータを準備できません。次のフレーム.
では、別のバッファが用意されていれば、CPU、GPU、ディスプレイ デバイスはすべて独自のバッファを使用して、互いに影響を与えずに動作できますが、問題は解決しますか?
トリプルキャッシュ
3 バッファリングは、ダブル バッファリング メカニズムにグラフィック バッファ バッファを追加することで、アイドル時間を最大限に活用できますが、もう 1 つのグラフィック バッファが占有するメモリが使用されるという欠点があります。
ジャンクが発生した場合 (B フレームが失われる)、2 番目の 16 ミリ秒の期間に、CPU/GPU は3 番目のC フレームの計算を完了しますが、A フレームはまだもう一度表示されますが、その後の表示は図に示すように、スムーズであり、ジャンクのさらなる悪化を効果的に回避します。
以上がAndroidの画面リフレッシュの原理です。
振付家
前述のように、Google は Android 4.1 システムで Android ディスプレイ システムを最適化しました。VSync パルスを受信すると、すぐに次のフレームのレンダリングを開始します。つまり、VSync 通知を受信すると、CPU と GPU はすぐに計算を開始し、データをバッファに書き込みます。このセクションでは、「VSync による描画」 - Choreographerの実装について説明します。
Choreographer は Java クラスであり、パッケージ パスは android.view.Choreographer です。クラスアノテーションは「アニメーション、入力、描画のタイミングを合わせる」ことです。
Android システムは、VSync 信号を受信した後にのみ描画を開始し、描画が完全に 16.6ms であることを保証し、描画のランダム性を回避します。
通常、アプリケーション レイヤーは直接 Choreographer を使用しませんが、ValueAnimator.start()、View.invalidate() など、アニメーションとビューの描画に関連するより高度な API を使用します。
業界では、一般に Choreographer を使用してアプリケーションのフレーム レートを監視しています。
アクティビティの起動プロセス中に onResume ライフサイクルが実行され、このActivityThread
プロセス中にメソッドが実行されますhandleResumeActivity
。
このメソッドでは、レイアウト DecorView が Window に追加され、
同時に、このプロセスは、ViewRootImplオブジェクトを作成し、setView
ViewRootImpl オブジェクトのメソッドを呼び出し、DecorView を ViewRootImpl の mView メンバー変数に割り当ててから、requestLayout()
メソッドを実行します。
requestLayout()
方法はscheduleTraversals()
方法に実行され、次に方法に進みperformTraversals()
、次に私たちがよく知っている3つの主要なプロセス、つまり測定、レイアウト、および描画に進みます。
さらに、ソースコードを見ると、ValueAnimator.start() と View.invalidate() を使用すると、最終的に ViewRootImpl の scheduleTraversals() メソッドに移動することがわかりました。つまり、すべてのUI 変更は ViewRootImpl の scheduleTraversals() メソッドに送られます。
次にまた質問があります。scheduleTraversals() と performTraversals() の間で何が起こったのでしょうか。すぐに実行されますか?
上記の紹介によると、描画は VSync 信号、つまり performTraversals() メソッドが到着したときにのみ実行されます。
1. まず、mTraversalScheduled フィールドを使用して、同時に複数の変更が 1 回だけ更新されるようにします。
2. 次に、現在のスレッドのメッセージ キュー Queue に同期バリアを追加します。これにより、通常の同期メッセージがシールドされ、前の同期メッセージを待つ代わりに、VSync が到着した直後に描画が実行されるようになります。
mChoreographer.postCallback() メソッドは、次のフレームで実行されるコールバックを送信するために呼び出されます。