M5StickCのDisplay周り解析 その4 LovyanGFXで高速描画

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

概要

前回はM5StickCの標準ライブラリの使い方を説明しました。今回はより高速動作するLovyanGFXの使い方を説明したいと思います。

LovyanGFXとは?

らびやんさんが公開しているグラフィックライブラリです。M5StickCのグラフィックライブラリであるTFT_eSPIとある程度の互換性をもち、高速描画に対応したライブラリです。

セットアップ方法

Arduino IDEのライブラリマネージャからのLovyanGFXで検索してインストールします。

スケッチ例を動かしてみる

「1_simple_use」スケッチを動かしてみて、動けば正常にインストールができています。

M5StickCライブラリとの共存

標準状態だとM5StickCの標準グラフィックライブラリと、LovyanGFXがバッティングして共存できません。

M5StickCの標準ライブラリと同等の動きを目指すキメラコアというものがありますが、これを使わずに標準ライブラリのグラフィックライブラリのみ置き換える方法を紹介したいと思います。

基本描画(GFX_Image_BMP16bit)

// フォント関連
#include <efontEnableJaMini.h>      // (どれか一つ有効化)第2水準相当の日本語フォントを読み込み 4千文字139KB
#include <efont.h>                  // 実際のフォントデータの読み込み

// LovyanGFX
#include <LovyanGFX.hpp>            // LovyanGFXの読み込み

// イメージデータ
#include "data.h"                   // 画像データの読み込み

// M5StickCライブラリとの共存設定
#define _M5DISPLAY_H_               // M5StickCとの共存用設定
class M5Display {};                 // 既存の描画関数は使えなくする
#include <M5StickC.h>               // M5StickCの読み込み
#include "utility/ST7735_Defines.h" // BLACKなどの定義を読み込む

static LGFX lcd;                    // 描画クラス

// FPS計算
static uint32_t sec;
static uint32_t psec;
static size_t fps = 0;
static size_t frame_count = 0;

void setup() {
  M5.begin();
  M5.Axp.ScreenBreath(12);          // 7-12で明るさ設定
  lcd.init();                       // 初期化
  lcd.setFont(&fonts::efont);       // efontを有効化
  lcd.setRotation(3);               // 0-3で画面の向き
  lcd.setSwapBytes(true);           // スワップON(色がおかしい場合には変更する)
}

void loop() {
  // 描画開始(明示的に宣言すると早くなる)
  lcd.startWrite();

  // 画像をランダムに表示
  int x = random(lcd.width());
  int y = random(lcd.height());
  lcd.pushImage(x, y, imgWidth, imgHeight, img);

  // FPS更新
  ++frame_count;
  sec = millis() / 1000;
  if (psec != sec) {
    psec = sec;
    fps = frame_count;
    frame_count = 0;
  }

  // 文字表示
  char str[256];
  sprintf(str, "GFXImgRGB565検証 %3d", fps);
  lcd.setCursor(0, 0);
  lcd.printf(str);

  // 描画終了
  lcd.endWrite();

  // Wait
  delay(1);
}

前回と同じような動作をLovyanGFXで再現したいと思います。

// フォント関連
#include <efontEnableJaMini.h>      // (どれか一つ有効化)第2水準相当の日本語フォントを読み込み 4千文字139KB
#include <efont.h>                  // 実際のフォントデータの読み込み

こちらは標準ライブラリとの同じくefontの日本語フォントを読み込んでいます。日本語フォントを使わない場合には必要ありません。

フォント周りの指定方法は、今後他のフォントも利用できるようなるようで、変更がはいりそうです。

// LovyanGFX
#include <LovyanGFX.hpp>            // LovyanGFXの読み込み

LovyanGFXライブラリを読み込みます。

// イメージデータ
#include "data.h"                   // 画像データの読み込み

前回と同じく画像データは別ファイルに分離しています。

// M5StickCライブラリとの共存設定
#define _M5DISPLAY_H_               // M5StickCとの共存用設定
class M5Display {};                 // 既存の描画関数は使えなくする
#include <M5StickC.h>               // M5StickCの読み込み
#include "utility/ST7735_Defines.h" // BLACKなどの定義を読み込む

ここの部分がキモになります。あまり好ましくない使い方ですので注意してください。

_M5DISPLAY_Hを事前に宣言しておくことで、標準ライブラリのグラフィックドライバーを読み込まないようにしてあります。その後に空のM5Displayクラスを作ってから、M5StickC.hを読み込んでいます。

最後のutility/ST7735_Defines.hはBLACKなどの色の宣言がされているファイルで、TFT_BLACKなどを使っている場合には読み込む必要はありません。

static LGFX lcd;                    // 描画クラス

LovyanGFXクラスを宣言しています。M5.Lcdクラスの変わりに、このlcdクラスを使って描画を行います。

  M5.begin();
  M5.Axp.ScreenBreath(12);          // 7-12で明るさ設定
  lcd.init();                       // 初期化
  lcd.setFont(&fonts::efont);       // efontを有効化
  lcd.setRotation(3);               // 0-3で画面の向き
  lcd.setSwapBytes(true);           // スワップON(色がおかしい場合には変更する)

初期化部分です。M5.Lcdをlcdに置換すればほとんど同じ処理で動きます。lcd.init()だけは追加で呼び出すようにしましょう。efontを使う場合にはsetFont()も呼び出す必要があります。

その他は前回のプログラムとほぼ一緒です。標準ライブラリで同等の描画をするとFPSが96ぐらいでしたが、LovyanGFXだと250弱と倍以上の速度がでます!

ダブルバッファ(GFX_DB_Image_BMP16bit)

// フォント関連
#include <efontEnableJaMini.h>      // (どれか一つ有効化)第2水準相当の日本語フォントを読み込み 4千文字139KB
#include <efont.h>                  // 実際のフォントデータの読み込み

// LovyanGFX
#include <LovyanGFX.hpp>            // LovyanGFXの読み込み

// イメージデータ
#include "data.h"                   // 画像データの読み込み

// M5StickCライブラリとの共存設定
#define _M5DISPLAY_H_               // M5StickCとの共存用設定
class M5Display {};                 // 既存の描画関数は使えなくする
#include <M5StickC.h>               // M5StickCの読み込み
#include "utility/ST7735_Defines.h" // BLACKなどの定義を読み込む

static LGFX lcd;                    // 描画クラス

// 画面ダブルバッファ用スプライト
static LGFX_Sprite canvas(&lcd);

// FPS計算
static uint32_t sec;
static uint32_t psec;
static size_t fps = 0;
static size_t frame_count = 0;

void setup() {
  M5.begin();
  M5.Axp.ScreenBreath(12);          // 7-12で明るさ設定
  lcd.init();                       // 初期化
  lcd.setFont(&fonts::efont);       // efontを有効化
  lcd.setRotation(3);               // 0-3で画面の向き
  lcd.setSwapBytes(true);           // スワップON(色がおかしい場合には変更する)

  // 画面ダブルバッファ用スプライト作成
  canvas.createSprite(lcd.width(), lcd.height());
  canvas.setSwapBytes(true);
  canvas.setTextEFont();            // efontを有効化
}

void loop() {
  // 描画開始(明示的に宣言すると早くなる)
  lcd.startWrite();

  // 画像をランダムに表示
  int x = random(lcd.width());
  int y = random(lcd.height());
  canvas.pushImage(x, y, imgWidth, imgHeight, img);

  // FPS更新
  ++frame_count;
  sec = millis() / 1000;
  if (psec != sec) {
    psec = sec;
    fps = frame_count;
    frame_count = 0;
  }

  // 文字表示
  char str[256];
  sprintf(str, "GFXDBImg565検証  %3d", fps);
  canvas.setCursor(0, 0);
  canvas.printf(str);

  // 描画
  canvas.pushSprite(0, 0);

  // 描画終了
  lcd.endWrite();

  // Wait
  delay(1);
}

ダブルバッファのサンプルです。

// 画面ダブルバッファ用スプライト
static LGFX_Sprite canvas(&lcd);

こちらもほとんど使い方は標準ライブラリと変わりません。上記のようにLovyanGFX用のスプライトを作成して描画します。

  // 画面ダブルバッファ用スプライト作成
  canvas.createSprite(lcd.width(), lcd.height());
  canvas.setSwapBytes(true);
  canvas.setTextEFont();            // efontを有効化

標準ライブラリの場合、スプライトのsetSwapBytes()はライブラリが古く間違っているのでfalseでしたが、LovyanGFXでは本来指定すべきtrueになります。

スプライトに対して日本語フォントを使う場合には、個別にsetTextEFont()で有効化する必要があります。

ダブルバッファを使う場合には、画面全体を毎回転送する必要があるので単純な例では標準ライブラリと、LovyanGFXではそれほど速度差がつきません。

回転(GFX_DB_ImageSpriteRotated_BMP16bit)

// フォント関連
#include <efontEnableJaMini.h>      // (どれか一つ有効化)第2水準相当の日本語フォントを読み込み 4千文字139KB
#include <efont.h>                  // 実際のフォントデータの読み込み

// LovyanGFX
#include <LovyanGFX.hpp>            // LovyanGFXの読み込み

// イメージデータ
#include "data.h"                   // 画像データの読み込み

// M5StickCライブラリとの共存設定
#define _M5DISPLAY_H_               // M5StickCとの共存用設定
class M5Display {};                 // 既存の描画関数は使えなくする
#include <M5StickC.h>               // M5StickCの読み込み
#include "utility/ST7735_Defines.h" // BLACKなどの定義を読み込む

static LGFX lcd;                    // 描画クラス

// 画面ダブルバッファ用スプライト
static LGFX_Sprite canvas(&lcd);

// 画像用スプライト
static LGFX_Sprite sprite(&lcd);

// FPS計算
static uint32_t sec;
static uint32_t psec;
static size_t fps = 0;
static size_t frame_count = 0;

void setup() {
  M5.begin();
  M5.Axp.ScreenBreath(12);          // 7-12で明るさ設定
  lcd.init();                       // 初期化
  lcd.setFont(&fonts::efont);       // efontを有効化
  lcd.setRotation(3);               // 0-3で画面の向き
  lcd.setSwapBytes(true);           // スワップON(色がおかしい場合には変更する)

  // 画面ダブルバッファ用スプライト作成
  canvas.createSprite(lcd.width(), lcd.height());
  canvas.setSwapBytes(true);
  canvas.setTextEFont();            // efontを有効化

  // 画像用スプライト作成
  sprite.createSprite(imgWidth, imgHeight);
  sprite.setSwapBytes(true);
  sprite.pushImage(0, 0, imgWidth, imgHeight, img);

}

void loop() {
  // 描画開始(明示的に宣言すると早くなる)
  lcd.startWrite();

  // 画像をランダムに表示
  int x = random(lcd.width());
  int y = random(lcd.height());
  int angle = random(360);
  canvas.setPivot(x, y);
  sprite.pushRotated(&canvas, angle);

  // FPS更新
  ++frame_count;
  sec = millis() / 1000;
  if (psec != sec) {
    psec = sec;
    fps = frame_count;
    frame_count = 0;
  }

  // 文字表示
  char str[256];
  sprintf(str, "GFXDBImgRT565検証%3d", fps);
  canvas.setCursor(0, 0);
  canvas.printf(str);

  // 描画
  canvas.pushSprite(0, 0);

  // 描画終了
  lcd.endWrite();

  // Wait
  delay(1);
}

回転した画像を描画するサンプルです。処理は標準ライブラリとほぼ同じで動きます。

標準ライブラリの場合に回転を行うと速度が3割程度低下しました。しかしながらLovyanGFXではほとんど速度低下していません!

標準ライブラリではできない拡大・縮小なども低負荷で描画することができます。

LovyanGFXの利点

  • 回転描画が高速
  • 拡大・縮小処理ができる
  • efontのマルチバイト描画をprintf()などで使える
  • 16色・256色BMP、PNG、JPEGなどのサポート

上記以外にもいろいろ機能追加がされています。色々追加されているので、標準のと見比べないとよくわからないのですが、複雑な描画を行う場合に標準ライブラリではかなり遅くなるところでも、LovyanGFXでは安定した描画が可能になっています。

画像フォーマットのサポートがかなり拡張されています。JPEGやPNGを使うことでかなりデータサイズを小さくすることが可能です。パレットデータにも対応しているので、16色画像などであればかなり小さくしたり、パレットデータの変更で色替えができたりします。

LovyanGFXの欠点

  • M5StickC標準ディスプレイライブラリとバッティングする
  • 機能がありすぎて使いこなせない

一番目は両方ディスプレイ用のライブラリなので仕方ないと思います。機能はほんとうにたくさんあるので、使いたい機能だけまずは使うのがいいと思います。

速度比較

スケッチFPS
M5_Image_BMP16bit96
M5_DB_Image_BMP16bit100
M5_DB_ImageSpriteRotated_BMP16bit65
M5_ImageFast111
M5_DB_ImageFast105
GFX_Image_BMP16bit251
GFX_DB_Image_BMP16bit105
GFX_DB_ImageSprite_BMP16Color102
GFX_DB_ImageSpriteRotated_BMP16bit100
GFX_ImageFast314
GFX_DB_ImageFast131

ざっくりとスケッチ別の秒間描画回数です。もう少し複雑な描画をすれば差はもう少し広がると思います。

今回紹介していないスケッチ例もあるので、気になった人はライブラリ内蔵のスケッチなども確認してみるといいと思います。

さらなる高速描画のために

スプライトから画面に書き込むときに裏側でDMA転送してくれますので、キャンバス用のスプライトに描画するのと、スプライトを画面に転送するのは同時の方が高速化できます。

複数のスプライトを作成し、画面に転送中のキャンバスと、次のフレームを描画するキャンバスにわけることで、転送待ちをせずに描画も行えます。

M5StickCの場合には画面が小さいので、複数画面のスプライトを作成可能ですが、M5Stackなどの場合には画面を縦3分割ぐらいして、上を描画して転送、転送中に真ん中を描画していると上画面の転送が終わっているので、続けて真ん中を転送などのように転送待ちをする必要がなくなります。

あとは、なるべく画面全体を転送するのではなく、変更頻度が違う場所は別スプライトに分割しておくとより早く転送が完了するはずです。

まとめ

かなり便利に使えるライブラリです。ただし、標準ライブラリとの棲み分けが難しくなると思います。

コメント

  1. ch より:

    いつもお世話になっております
    M5StickC.h との共存が出来なくって泣きそうになりましたが
    こちらのサイトで助けられました
    今後ともよろしくお願い申し上げます
    https://qiita.com/chrmlinux/items/c28dfa827f6b0f52dea8