LovyanGFX入門 その4 描画の仕組み

概要

前回は日本語フォントでしたが、今回はLovyanGFXを使う上で知っておいた方がよい描画周りの仕組みを解説したいと思います。LovyanGFXは複数の環境で動きますが、今回はESP32に特化した内容になります。

概要説明なので、適当に端折ったところがあり厳密には間違っているところがあると思いますのでご注意ください。

ゲーム機などの描画タイミング

秒間30フレームの場合、1フレームの描画は約33ミリ秒になります。コントローラー入力などの内部処理を行い、画面をバッファに描画してから最後に次の描画まで待機しています。描画タイミングをフレーム同期やF SYNCと呼んだりもします。

内部処理や描画処理が遅くなり、33ミリ秒で描画が終わらない場合には処理途中のものが描画されるか、一個前のフレームが描画されちゃいます。シューティングなどで処理落ちが発生する場合などはこんな理由でしょうか?

Arduinoでの描画タイミング

Arduinoの場合には、描画のタイミングがわからず不定期に描画されちゃいます。厳密には定期的に実行されているのですが、いつ描画を行ったのかわかりません。

TFT的にはF SYNCの信号があるのですが、ボードに接続されていないことが多く、CPUからはいつ描画が走っているかがわかりません。なので画面クリアした直後に描画が走ると、一瞬画面が黒くなります。画面クリア系は転送量が多いので時間がかかり、比較的描画されちゃうことが多くなります。これがチラツキの原因となります。

ゲーム機などのチラツキ防止方法

フレーム同期を使えば最適なタイミングで描画できるのですが、描画中にデータを書き換えてしまうと問題が出てしまいます。たとえば描画バッファが1024×1024ある場合、画面用として640×480を2枚確保して、残りに画面に描画する素材などを置いておきます。

描画バッファ0を画面に転送しているときには、描画バッファ1に次のフレームを書き込みます。フレーム同期のタイミングで、画面転送元を描画バッファ1に切り替えて、今後は次のフレームを描画バッファ0に書き込みます。

このように交互に書き込んでいくことで画面のチラツキを抑制しています。

M5Stack Core2の場合(ILI9342C)

TFTドライバには320×240が確保されており、交互に書き込むことなどはできません。

M5StickCの場合(ST7735S)

TFTドライバに132×162のメモリがありますが、接続している画面サイズの80×160しか利用していません。それ以外は未使用です。M5StickC Plusは135×240と画面が大型化し、ST7735Sの扱えるサイズを超えてしまったので、ST7789V2にTFTドライバが変更になりました。

Arduinoでのチラツキ防止方法

Arduinoの場合にはTFTドライバ側に描画バッファを複数確保できないため、メモリ上に描画バッファを確保して、描画し終わったものを一括転送することによってチラツキを抑えることができます。

ただし、320×240の場合に16ビットの描画バッファを確保するためには320x240x2で153,600Byteのメモリが必要です。ESP32の場合には520KBのメモリを搭載しているため、がんばれば確保できそうに思えますが実際には確保できません。

ESP32(M5Stack)の構成

ざっくりした画面に関連する構成図です。ESP32はSoCと呼ばれるCPUとメインメモリを一体化したチップですが、通常使われるのはフラッシュメモリを内蔵したESP32-WROOM-32などのモジュールです。さらにPSRAMを内蔵したESP32-WROVERというモジュールもあります。

M5Stack Coreの場合、ESP32 SoCから複数のSPI接続が存在しており、内蔵フラッシュメモリと、SPI接続の外付けメモリ、TFTとSDの両方が接続されている3系統のSPIが存在しています。

SPIは系統が違えばDMAなどでの同時アクセスが可能ですが、TFTとSDは同じSPI系統に接続されているので同時利用ができません。

また、内蔵メインメモリがSRAM0から2まであります。この3つのメインメモリはメモリアドレスが連続していないので、合計520KBですがくっつけて大きなメモリを確保することはできません。小さなメモリをたくさん確保するのであれば問題ありませんが、大きなメモリを確保しようとすると分割されているので、空き容量と実際に確保できる最大量で差ができます。

そしてESP32の場合、一つの変数で確保できるのは概ね100KB程度までです。つまり320×240の描画バッファをメインメモリ上に確保することはできません。

SPI接続のメモリであるPSRAMが利用できる場合には、4MBまではメインメモリと同じように利用することが可能です。この場合には320×240の描画バッファをかなりの枚数確保することが可能です。ただし、SPI接続のメモリのため原理上アクセスが遅く、PSRAMを読み出してTFTに書き込むとなるとSPIの転送時間が2倍かかることになります。

チラツキのみを低減する場合にはPSRAMを利用するのは非常にお手軽です。速度を求める場合には他の方法を検討する必要があります。

描画エリアの限定or分割

グラフやゲーム画面などの動的に書き換わる場所が固定されている場合には、描画バッファを分割することができます。画面全体分を確保するのではなく、部品単位に分割することでメインメモリ上にバッファが確保できます。このサイズであれば2枚確保して、交互に転送ができる可能性があります。

分割描画

全面書き換え時にLovyanGFXが推奨している方式です。TFTの描画エリアを3つに分割します。そしてメインメモリ上に分割した描画エリアを2つ確保します。

#描画転送
1メモリ描画バッファ0にTFT描画エリア0を描画
2メモリ描画バッファ1にTFT描画エリア1を描画メモリ描画バッファ0からTFT描画エリア0に転送
3メモリ描画バッファ0にTFT描画エリア2を描画メモリ描画バッファ1からTFT描画エリア1に転送
4メモリ描画バッファ0からTFT描画エリア2に転送

ちょっとわかりにくいのですが、上記の順番で処理をします。メモリ描画バッファからTFT描画エリアへの転送はバックグラウンドのDMA転送が使えますので、転送している間に次のエリアをメモリバッファに描画しておきます。

縦に3分割していますので、320×80が2画面分確保しますので合計100KBぐらいのメモリを利用します。複数のメモリ描画バッファが確保できれば、バックグラインドのDMA転送が使えるので、効率的に転送が可能になります。

まとめ

今回はLovyanGFX入門といいつつ、LovyanGFXに特化した内容ではありません。次回以降に実際にLovyanGFXを利用して、効率的な描画を行う方法をまとめたいと思います。

続編

コメント