メイン・コンテンツまでスキップ

パフォーマンス

このセクションでは、組込みグラフィカル・ユーザ・インタフェースのパフォーマンスについて説明します。

ここでの高パフォーマンスとは、必要なグラフィカル効果とアニメーションも実現しながら、高いフレーム・レートを達成できている状態として定義されます。

前のセクションで説明した、ユーザ・インタフェースのフレーム・レートがメイン・ループから受ける影響について思い出してみましょう。 今回も、パラレルRGBディスプレイがLTDCおよび2つのフレームバッファと接続されていると仮定します。 基本的な状況は以下のとおりです。

ダブル・フレームバッファ

ディスプレイは、毎秒60回リフレッシュされると想定されるので、各リフレッシュ間隔はおよそ16 msです。 次のような計算になります。 1秒/ 60 = 0.01667秒= 16.67 ms

TouchGFXは、フレームバッファ1の転送が開始した時点で、フレーム1をフレームバッファ2に描画することを開始します。 次の転送が開始される前に、フレーム1の描画が終了すれば、フレームバッファ2を転送することができます。 16.67 ms以内に描画が終了しない場合、フレームバッファ1が再度転送されるので、ディスプレイの表示は変わりません。

メイン・ループの時間が16.67 msを超える場合

この状況はロスト・フレームと呼ばれます。

収集および更新フェーズにかかる時間は通常は極めて短い(1 ms未満など)ので、メイン・ループにかかる時間全体を考えれば、事実上無視することができます。 したがって、以下の説明でも一般的にも、描画時間を考える際、そこには収集および更新フェーズを含めます。

多くのフレームの描画時間が16.67 msの制限を超える場合、ディスプレイのフレーム・レートは毎秒30フレーム(30 fps)になります。

描画時間が全般的に16.67 msを下回るものの、一部のフレームで16.67 msよりも長くかかる場合は、平均フレーム・レートは60 fpsに近づくことになりますが、アニメーションがユーザに滑らかに表示されない可能性があります。 アプリケーションによっては、アニメーションのステップの一部が速く表示されたり、遅く表示されたりする可能性もあります。 これは望ましい現象ではありません。

描画時間が長くなる可能性もあります。 33 msを少し上回ると、新しいフレームが準備されるのが転送3回ごとになり、フレーム・レートは20 fpsに低下します。

FPS最大描画時間
6016.67 ms
3033.34 ms
2050.00 ms
1566.67 ms

この表は、所定のフレーム・レートに対応できる最大描画時間(収集および更新フェーズを含む)を示しています。

ユーザ・インタフェースの最適なパフォーマンスを実現するには、フレーム・レートを定期的にチェックして監視することが非常に有益であると思われます。 次の2つのアプローチを利用できます。

  • 描画時間の測定
  • ロスト・フレームのカウント

描画時間の測定

描画時間を測定するという最初のアプローチは、最も詳細な情報をもたらします。 基本的な考え方は、フレーム転送から描画フェーズ終了までの時間を測定することです。 グラフィック・エンジンは収集フェーズの開始時にGPIOクラスの関数を呼び出し、描画フェーズの終了時に別の関数を呼び出すようにします。 アプリケーションはこれらの関数を定義し、測定を実行できるようになります。

測定は以下の2つの方法で実行できます。

  • オシロスコープなどの外部の計時デバイスを使用: オシロスコープを使用するには、アプリケーションがset(GPIO_ID)メソッドと、clear(GPIO_ID)メソッドを、GPIOインタフェースから実装する必要があります。 これでオシロスコープは、描画時間を出力がハイのときの経過時間として測定できます。
  • 内部タイマを使用: もう1つのアプローチでは、sysTickタイマなどの内部タイマを使用します。 GPIO::set(RENDER_TIME)が呼び出されると、アプリケーションはタイマの値をある変数に保存できます。 クリア・コールが行われると、アプリケーションは再度タイマを読み取り、前の値を差し引いて描画時間を取得できます。 タイマの速度によって測定の解像度が決まります。 アプリケーションは、何らかの方法で描画時間を表示する必要があります。 1つの方法として、値をグローバル変数に保存し、その値をスクリーン上のテキスト・エリアに表示することが考えられます。 この値はデバッガでチェックすることもできます。

ロスト・フレームのカウント

グラフィック・エンジンは、直前の収集-更新-描画フェーズで発生した転送の回数をカウントします。 アプリケーションはこの値をチェックして、フレームが失われてフレーム・レートが低下していないかどうか、簡単に確認することができます。

カウントはHALクラスで利用できます。

void handleTickEvent() {
tickCounter += 1;
if (HAL::getInstance()->getLCDRefreshCount() > 1) {
//Alert programmer somehow
...
}
}

ロスト・フレームの補正

フレームが失われ、いずれかのアニメーションのフレーム・レートが低下した場合は、ある程度まで補正することができます。 以下のいずれかを実行します。

  • 待ち続ける - アニメーションをそのまま続けます。アニメーションの時間が長引き、スムーズでなくなる可能性もあります。
  • 一部のフレームをスキップ - アニメーション全体の時間が予定より長くならないように、一部のフレームをスキップします。

フレームが失われた場合、一部のフレームを自動的にスキップするように、TouchGFXを設定できます。 このためには、実際のフレームあたり複数回アニメーションをティックします。 これにより、描画時間が不均等な場合でも、アニメーションをより滑らかにできます。

HAL.hpp
void setFrameRateCompensation(bool enabled)

描画時間への影響要因

描画時間には多様な要因が影響します。更新部分のサイズ、レイヤ化の利用、ウィジェットの複雑さ、描画をサポートするハードウェアなどです。

更新されるスクリーンの量

描画時間は一般に、更新の必要があるピクセル数に比例します。 つまり、アニメーションの描画に時間がかかりすぎている場合、考えられる修正はアニメーションの領域を減らすことです。 たとえば、回転する画像があり、パフォーマンスが十分でない場合、その画像のサイズを小さくすることでパフォーマンスを向上させることができます。

画像サイズを小さくすることで描画時間を短縮

グラフィック・エンジンは、アプリケーションが無効化した領域を再描画することを思い出してください。 つまり、実際にリフレッシュが必要な領域のみを無効化することが重要です。

無効化領域が大きいほど、描画時間が長くなります。

グラフィックス内のレイヤ数

一般的なアプリケーションでは、グラフィックスは互いに積み重なるさまざまな要素で構成されます。 要素のいずれかが更新されると、通常はすべての要素を再描画する必要があります。

この代表的な例がバックグラウンド画像、フレーム、いくつかのテキストです。

グラフィック要素のレイヤ化

このユーザ・インタフェースは、透明なフレームを表示するImageウィジェットの上にTextAreaウィジェットを配置しています。 どちらもバックグラウンド画像上にあります。

TouchGFX Designerでのグラフィック要素のレイヤ化

このソリューションはアプリケーションで非常によく使用されます。 これは実は柔軟性の高い非常に容易なソリューションで、たとえば、実行時にフレームを変更したり、バックグラウンド上でフレームやテキストを移動したりできます。

描画時間に関する問題は、実行時にテキストが更新され、再描画の必要が生じた場合、グラフィック・エンジンはバックグラウンドとフレームも再描画し、それから新しいテキストを再描画する必要があることです。 このためにテキストの描画時間が大幅に増大します。

無効化領域内のレイヤが多いほど、描画時間が長くなります。

ピクセル描画の複雑さ

フレームバッファへの描画の難しさは、すべてのピクセルで同じわけではありません。 どのタイプの描画でも、グラフィック・エンジンは計算結果のピクセルをフレームバッファに書き込む必要があります。 しかし、書き込むピクセルの計算時間はさまざまです。

Boxウィジェットなどで使用される固定の色の計算時間は低くなります。ピクセルの計算は一度だけ行われ、すべてのピクセルに再利用されるからです。 つまり、多数のボックスを使用すると、パフォーマンスは非常に高くなります。 ただし、高品質のユーザ・インタフェースにならないので、この方法は推奨されません。

ピクセルの計算時間が次に低いのは画像です。ピクセルがすぐに使用可能なフォーマットでビットマップに保存されているからです。 フレームバッファに書き込むピクセルの計算で問題になるのは、色の値をビットマップ内の正しい位置から読み込むことです。

テキストは画像よりも時間がかかります。個々の文字が実際には小さな画像として表現されているからです。 実際のところ、時間がかかるようになるのは、多数の小さな画像があるために「開始-停止」の時間が大幅に増えるためです。 たとえば、個々の文字位置の計算があります。 テキストの見栄えをできる限りよくするために、テキストは透明性のある小さな画像で表現されます。透明度に関する下の注を参照してください。

回転画像や拡大画像は上記よりも時間がかかります。 ここでのタスクはビットマップからピクセル値を再び読み込むことですが、こちらの計算の方が時間がかかります。グラフィック・エンジンが画像のスケーリングや回転を組み込む必要があるからです。

円などの幾何学的要素はさらに時間がかかります。 今度はピクセルの色をビットマップから読み込むことができず、円の形状と、円の中の個々のピクセルの色を計算する必要があるからです。

透明度は要素の描画時間を増やします。 一部のピクセルが塗りつぶされていない場合、要素は透明になります。 このことが描画時間を増大させます。グラフィック・エンジンは最初に、透明な要素の背後にある要素を描画する必要があるからです(「フレーム内のテキスト」に関するセクションを参照)。 次にグラフィック・エンジンは、バックグラウンドのピクセルと透明な要素のピクセルを結合させ、結果をフレームバッファに書き込む必要があります。 計算済みのピクセルをただ書き込むよりも、この計算にははるかに時間がかかります。

ボックス、画像、回転画像、円。 上の行は塗りつぶしの要素。 下の行は透明な要素。

透明度を持たせると、常に余分なレイヤが必要になります。 ただし、塗りつぶしのピクセルを他の塗りつぶしピクセルの上に配置しても、必ずしもレイヤの数が増えるわけではありません。 グラフィック・エンジンは、時間の無駄を防ぐために、他の塗りつぶしピクセルでカバーされるピクセルは描画しないようにします。

無効化領域内に時間がかかる要素が多いほど、描画時間が長くなります。

描画時間を増やすのは、無効化領域にある要素のみであることを思い出してください。 無効化領域以外にある要素は、描画時間に影響を及ぼしません。

UIコンポーネントとパフォーマンスの詳細については、こちらを参照してください。

描画をサポートするハードウェア

一部のSTM32マイクロコントローラには、Chrom-ART(またはDMA2D)と呼ばれるグラフィック・アクセラレータが含まれています。 このアクセラレータは描画時間を短縮できます。 このアクセラレータはマイクロコントローラのコアと並列実行されるので、アクセラレータがグラフィックスを描画する間に、マイクロコントローラは他のタスクを自由に実行できます。

Chrom-ARTは、主に画像とテキストに対して有用です。 Chrom-ARTが実装されている場合に、グラフィック・エンジンによって自動的に使用されます。

描画時間を考慮する必要がある場合

描画時間は、いつも同じように重要なわけではありません。 低速なフレーム・レートがユーザの目に触れるようになった場合には、描画時間に注意を払う必要があります。 通常これは、スクリーンの一部でアニメーション(回転アイコンなど)を実行している場合や、スクリーン上で何かを移動またはスライドさせている場合などです。 更新頻度が低くなると、ユーザへの表示が滑らかでなくなり、段階的に表示されます。 こうした場合には、描画時間をチェックする必要があります。

一方で、スクリーン全体を新しいスクリーンに置き換える場合、変更中にフレーム・レートが大幅に低下しても、通常そのことはユーザにはわかりません。 ユーザは描画の開始時がわからず、終了時のみがわかるからです。

上記の2つのルールは、(移動などの)アニメーション化する要素には、わずかなレイヤを使用して、複雑な要素や多数のレイヤの使用を控える必要があることを意味しています。 スクリーンの他のパーツでは、このことは問題ではないと思われます。

アナログ・クロックとスクロール・リスト

この例では、左側にアナログ・クロックがあります。 時計の3本の針は、回転する小さく細長い画像によって描画されています。 針は常に動いているわけではないので、通常はこれで大丈夫です。 しかし、スクリーン上でクロックが動き回るようにする場合は、すべてのフレームで再描画することになり、問題が生じる可能性があります。回転画像の描画は、典型的に時間がかかるからです。

右側にはスクロール・リストがあります。 ユーザは、この数字のリストを上下に移動させるので、ユーザ・インタフェースが反応よく表示されるように、高いフレーム・レートが必要です。 このため、スクロール・リスト内の要素の描画時間を考慮するか、スクロール・リストのサイズを小さくすることが重要になります。

コンテンツの無効化によるパフォーマンスの最適化

通常はウィジェット全体が無効化されますが、グラフィック・エンジンには、ウィジェット全体ではなく、ウィジェットのコンテンツのみを無効化する機能があります。 無効化する領域を減らすことで、描画時間が明らかに短縮されることがよくあります。 描画時間の改善は以下によって異なります。

  • ウィジェット全体のサイズに対して、そのウィジェットのコンテンツがカバーする領域のサイズの割合
  • そのウィジェットがバックグラウンドのウィジェットを一部カバーするのか、完全にカバーするのか

以下の図は、例としてTextAreaウィジェットを使用した場合の、コンテンツの無効化の概念を示しています。 図1は、ウィジェット全体の領域を示しています。 図2は、TextArea::invalidate()を使用したときに無効化される領域を示しています。 図3は、TextArea::invalidateContent()を使用したときに無効化される領域を示しています。

図1: スクリーンの幅全体に広がるTextArea

図2: TextArea::invalidate()を使用したときの無効化領域(赤色)

図3: TextArea::invalidateContent()を使用したときの無効化領域(緑色)

TextArea::invalidateContent()を使用する例

ウィジェットが他のウィジェットにオーバーラップしている場合、TextArea::invalidate()を使用してTextArea全体を無効化すると、こうした他のウィジェットを再描画する必要があります。 代わりにTextArea::invalidateContent()を使用することで、ウィジェットの不要な無効化や再描画のリスクを最小限に抑えることができます。 これは、CircleやGaugeなど、描画コストのかかるウィジェットの場合は特に当てはまります。

次の図は、TextArea::invalidateContent()を使用して、バックグラウンドのウィジェット(画像- STロゴ)の無効化を避ける方法を示しています。 TextArea::invalidate()を使用していれば、バックグラウンドのウィジェットは無効化され再描画の必要がありました。

TextArea::invalidateContent()を使用する例

最適なパフォーマンスを得るためのヒント

このセクションの最後に、最適なパフォーマンスを得るためのヒントをまとめます。

  • 変更ないものを再描画しない: ディスプレイの不必要なパーツを誤って無効化しないようにしてください。 パフォーマンスが低下し、何もいいことはありません。
  • 品質と速度のバランスを見いだす: 要素の複雑さを軽減することで、パフォーマンスを向上できます。 複雑さとパフォーマンスとの適切なバランスが、鍵となることは多くあります。
  • ハードウェアの機能を利用する: 多くの場合、ハードウェア・アクセラレーション(Chrom-ART)搭載のマイクロコントローラは、搭載なしのマイクロコントローラより高機能です。 Chrom-ART搭載のマイクロコントローラの使用を検討してみてください。
  • 計算されたグラフィックスを画像に置き換える: 円を計算して描くことは、円の画像を使用するよりも低速です。 一般に、画像は多くの静的要素と置換できます。
  • ディスプレイのリフレッシュ・レートを調整する: このセクションの最初で説明したように、リフレッシュ・レートは描画時間に対して厳しい制限になります。 描画時間がリフレッシュ・レートを超えると、フレーム・レートが低下します。 描画時間がリフレッシュ・レートを少しだけ上回る場合は、ディスプレイのリフレッシュ・レートを、たとえば55 Hz(18.2 msに対応)まで下げて、高いフレーム・レートを維持することができるかもしれません。