M5Stack Core2でM5GFXのスプライトの描画速度を測ってみた
目的
M5GFXでスプライトを使った処理を行いたいのだけど、どのくらいのパフォーマンスが出るのか知りたかったので、ベンチマークしてみました。画像のサイズは160x120、220x220、320x240の3パターンで、それぞれPSRAMを使わない場合と使った場合の組み合わせです。画像は16ビットのRGB565フォーマットを使用しました。
displayに直接pushImageした場合
canvasにpushImageしたとき
canvasからdisplayにpushSpriteした場合
canvasからdisplayにpushRotateZoomWithAAした場合
および、上記のcanvasからcanvasへの場合
今回のテストの目的は正確な値を調べることではなく、どの部分がボトルネックになっているのかを調べることが目的です。
コード
#include <M5Unified.h>
#include <M5GFX.h>
M5GFX display;
M5Canvas canvas;
M5Canvas canvas2;
// 画像データ
#include "zunda.hpp"
#include "pgmspace.hpp"
// デバッグに便利なマクロ定義 --------
#define sp(x) Serial.println(x)
#define spn(x) Serial.print(x)
#define spf(fmt, ...) Serial.printf(fmt, __VA_ARGS__)
const unsigned short* imgDatas[] PROGMEM = {img2, imgBin0, img1};
int xx[] = { 160, 220, 320 };
int yy[] = { 120, 220, 240 };
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
unsigned long sta, tm1, tm2, tm3;
int i;
sp("Start");
display.init();
display.clear(TFT_YELLOW);
// サイズごとのループ
for (int size=0; size<3; size++) {
// ディスプレイに直接出力
tm1 = 0;
for (i=0; i<100; i++) {
sta = micros();
display.pushImage(0,0, xx[size], yy[size], imgDatas[size]);
tm1 += micros() - sta;
}
spf("display: size=%dx%d psram=%d pushImage=%.1f ms\n", xx[size], yy[size], 0, (double)(tm1/100000));
// キャンバスからディスプレイへ出力
for (int psram=0; psram<2; psram++) {
if (size==2 && psram==0) continue; // 320x240はPSRAM必須
canvas.setPsram(psram==1);
canvas.setColorDepth(16);
canvas.createSprite(xx[size], yy[size]);
canvas.fillSprite(TFT_WHITE);
tm1 = tm2 = tm3 = 0;
for (i=0; i<100; i++) {
sta = micros();
canvas.pushImage(0,0, xx[size], yy[size], imgDatas[size]);
tm1 += micros() - sta;
sta = micros();
canvas.pushSprite(&display, 0,0);
tm2 += micros() - sta;
sta = micros();
canvas.pushRotateZoomWithAA(&display, xx[size]/2, yy[size]/2, 0, 0.93,0.93);
tm3 += micros() - sta;
}
spf("canvas->display: size=%dx%d psram=%d pushImage=%.1f ms, pushSprite=%.1f ms, pushRotateZoomWithAA=%.1f ms\n"
, xx[size], yy[size], psram, (double)(tm1/100000.0), (double)(tm2/100000.0), (double)(tm3/100000.0));
canvas.deleteSprite();
}
// キャンバスからキャンバスへ出力
for (int psram=0; psram<2; psram++) {
if (size==2 && psram==0) continue; // 320x240はPSRAM必須
canvas.setPsram(psram==1);
canvas.setColorDepth(16);
canvas.createSprite(xx[size], yy[size]);
canvas.fillSprite(TFT_WHITE);
canvas2.setPsram(psram==1);
canvas2.setColorDepth(16);
canvas2.createSprite(xx[size], yy[size]);
canvas2.fillSprite(TFT_WHITE);
tm1 = tm2 = tm3 = 0;
for (i=0; i<100; i++) {
sta = micros();
canvas2.pushImage(0,0, xx[size], yy[size], imgDatas[size]);
tm1 += micros() - sta;
sta = micros();
canvas2.pushSprite(&canvas, 0,0);
tm2 += micros() - sta;
canvas.pushSprite(&display, 0,0);
sta = micros();
canvas2.pushRotateZoomWithAA(&canvas, xx[size]/2, yy[size]/2, 0, 0.93,0.93);
tm3 += micros() - sta;
canvas.pushSprite(&display, 0,0);
}
spf("canvas->canvas: size=%dx%d psram=%d pushImage=%.1f ms, pushSprite=%.1f ms, pushRotateZoomWithAA=%.1f ms\n"
, xx[size], yy[size], psram, (double)(tm1/100000.0), (double)(tm2/100000.0), (double)(tm3/100000.0));
canvas2.deleteSprite();
canvas.deleteSprite();
}
sp("");
}
sp("Finish");
}
void loop() {
delay(10);
}
結果
以下は出力結果をChatGPTにまとめてもらったものです。(内容が合っているかは全くチェックしてません)
考察
この結果からわかることは、PSRAMを使うと遅くなるものの、体感的に気にするほどの遅さではなさそうです。320x240で使う場合はメモリが足らないのでPSRAMは必須です。アンチエイリアスを使った拡大縮小はさすがに重いですね。
私は今キャラクターを動かすプログラムを作っているのですが、この結果から考えるベストな方法は、
・キャラクターは 200x240 で表示する
・背景(320x240)は3分割する
・中央の背景はキャラクターと重ね合わせるためにスプライト化する
・左右の背景は固定としてディスプレイに直接描画する
・PSRAMを使用する
こうすれば、パフォーマンスと外観を両立できそうです。
余談
NoteってMarkdownの表が使えないんですかね?
調べると表計算ソフトからコピペすると表が作成できると聞いたので、コピペしたら画像になってしまいました。