RX65N Envision Kit オーディオプレイヤー(その2)

picojpeg デコーダーのポーティングが出来たので、ID3 タグ内のジャケット画像などを格納するタグ「APIC、PIC」内データ(JPEG)をデコードして画像表示は出来たのだが、画面サイズにフィットさせるのは後回しになっていた。

まず、仕組みを考える・・

画面デザインから、画面の右端に272×272ピクセル(液晶の縦ピクセル)で表示させるのが良さそうだ。
ただ、タグ内の画像サイズは様々なので、適切にスケーリングする必要がある。
スケーリングの方法も重要だ、RX65Nの内蔵メモリはあまり大きく無いので、画像のような大きなデータを扱う場合、出来れば、一時メモリを使用しない(利用できない)方が良い。

それらを踏まえて、画像のデコーダーテンプレートは、直接画像の操作を扱うAPIを叩かないで、中間に「スケーリング」を行うオブジェクト(クラス)を入れる事にした、こうしておけば、レンダリングを行う対象がどんな場合にも対応可能となる。

    typedef graphics::render<uint16_t, LCD_X, LCD_Y, AFONT, KFONT> RENDER;
    RENDER      render_(reinterpret_cast<uint16_t*>(0x00000000), kfont_);
    typedef img::scaling<RENDER> PLOT;
    PLOT        plot_(render_);
    typedef img::picojpeg_in<PLOT> JPEG_IN;
    JPEG_IN     jpeg_(plot_);

C++ では、「ファンクタ」と呼ばれる方法(通常「()」オペレーターを使う)で、クラスに対しての操作を一般化できる。

//-----------------------------------------------------------------//
/*!
    @brief  描画ファンクタ
    @param[in]  x   X 座標
    @param[in]  y   Y 座標
    @param[in]  r   R カラー
    @param[in]  g   G カラー
    @param[in]  b   B カラー
 */
//-----------------------------------------------------------------//
void operator() (int16_t x, int16_t y, uint8_t r, uint8_t g, uint8_t b) noexcept
{
・・・
}
まず、plot_ クラスに対して、スケールを設定する。
※スケールは、「整数比」で設定する。
※画像全体を収める為、縦か横で長い方を基準にする。

    auto n = std::max(ifo.width, ifo.height);
    plot_.set_scale(272, n);

jpeg_ クラス内から、plot_ クラスに対して、以下のように普通に描画すれば、設定された拡大、縮小比で描画される。
※ワークメモリを使わないのと引き換えに、描画する領域は、あらかじめ「0」クリアしておく必要がある。

    plot_(x, y, r, g, b);

また、描画時にオフセットを与えられるようにしてある。

    plot_.set_offset(vtx::spos(480 - 272, 0));

最初、縮小をさせる場合に、エイリアシングを考慮しない簡易的な実装を行ってみたのだが、当然のように描画品質はあまり良くない。
※ジャギジャギしてる・・

そこで、ピクセルの加重割合を考えずに平均化してみる事にした。
・フレームバッファはRGB565となっている。
・フレームバッファを読み出して、「0x0000」なら、初期に書き込むピクセルとして、そのまま書き込む。
・もし、新規に書き込むピクセルが「0x0000」なら、「0x0001」に直して書き込む。
・フレームバッファを読み出して、「0x0000」以外なら、既に書き込まれている値なので、新規に書き込む値との平均(1/2)を求めて書き込む。

この実装は、厳密な意味では正確ではないものの、「ジャギー」感が減り、かなりマシに見える。
とりあえず、これで良しとした、ピクセルサイズによる厳密な加重平均や、フィルタ的要素は、次の課題とする。

※現状では、「拡大」する場合は、簡易的なものになっていて「ジャギー」が目立つ。

RXマイコンの最適化に対する知見

RX65N Envision Kit が発売されてもうじき1年がたとうとしているが、アプリケーションを作っている人は非常に少ないように感じる・・
サーチエンジンで探しても、あまり有益な情報を探せない、現在、秋月では売り切れとなっており、それなりに購入している人はいると思うのだが、何か面白いアプリを作って発表し、ソースコードを公開している人はほとんどいないように感じる。
何がハードルになっているのか?・・・
それに比べて、M5Stack、ESP32、ARMなど海外のマイクロコントローラーは、非常に沢山あるようなのだが・・・
※何かあったら、リンクを教えて下さい。

さて、以前に「Arduino 用レイトレーサー」を実装している人がいて、コードが公開されていたので、RX65Nにポートしてみた。
その過程で、ルネサス公式のフォーラムにそのリンクを張った。
しばらくして、色々な指摘を受けた。
※自分のブログにも同じような指摘が返信されていたが、検証の材料が無く、ほぼスルー状態だった。
※自分の間違った知見もある・・

ルネサスのフォーラムでは、具体的な実験を行い、検証を行ってくれた。(感謝)
※単に思いつきで返信するだけじゃなく、このような、有益な内容のある濃い情報が重要だ、言うだけなら誰でもできる、何か発信するなら、せめて、自分で「手」を動かして、検証可能な情報を返して欲しいものだ。

そこで、指摘された事についてまとめておきたい。

  • gcc では RXv2 コアの最適化が不十分
  • double 定数を使っているのが余計
  • std::sqrt はdoubleの関数なので、sqrtf を使った方が良い
  • RXv2には、fsqrt 専用命令が用意されている
  • ESP32 などで公開されているベンチマークは、レイのサンプリングは「1」で行っているがRXでは、「4」で行っていて条件が異なる

gcc の RXv2 の最適化に関しては、自分の考え方が少しある。
まず、gcc のオフィシャルなソースコードを使う事を前提としているので、ルネサス社が、gcc に最適化のコードをコミットするまで待つしかない・・
現在ルネサス社は、独自にGNUツールを公開しているが、最新版は4.8系となっており、C++ の最新のコードをコンパイルする事が出来ない。
※ルネサス社が提供するGNUツールでコンパイルした場合、最適化「-O3」で、最大7~15%くらい良いコードが出る場合があるようだが、これは今後検証してみたい。
※オフィシャルな gcc では、「-mcpu=rx64m」など専用デバイスのオプションは無いが、標準で、RX600 系のコード出力となっている。

double の定数や、double 専用 API は、指摘を受ける前に既に、変更済みで、それで、ほんの少し高速になっていた。

標準の数学ライブラリでは、平方根を求める「sqrt、sqrtf」はニュートン法を使っており、RXv2 に備わっている、fsqrt 専用命令とは微妙に計算結果が異なっているものと思うので、標準では使わないのだと思っていた。
だが、この命令に切り替えた場合、倍の速度でレンダリングするようなので、この知見を素直に受け入れた。

#if defined(SIG_RX64M) || defined(SIG_RX71M) || defined(SIG_RX65N) || defined(SIG_RX24T)
static inline float sqrtf_(float x)
{
    __asm __volatile(
        "fsqrt %0, %0\n" \
        : "+r"(x) \
    );
    return x;
}
static inline int ceilf_(float x)
{
    int y;
    __asm __volatile(
        "pushc fpsw\n"
        "mvtc #0b10, fpsw\n"
        "round %0, %0\n"
        "popc fpsw\n"
        : "=r"(y) \
        : "r"(x) \
    );
    return y;
}
#else
static inline float sqrtf_(float x) { return sqrtf(x); }
static inline int ceilf_(float x) { return ceilf(x); }
#endif

レイのサンプリング数に関しては、全くノーマークだった、元ソースでは、標準「4」だったので、素直に「4」で行っていたが、ベンチマークを行っている Arduino のスケッチでは、「1」にして API を呼んでおり(卑怯なのではw)、大体4倍のレンダリング時間となっていた。
※「STM32F7 200MHz」では、0.62秒と非常に高速なので、この違いは、何なのか、疑問に思っていた・・・

以上の知見を受け、RX65N Envision Kit でレンダリングすると・・・

約0.83秒と、STM32F7 200MHz と比較しても、十分戦闘力のある値となったw
元は7.7秒とかだったので、素晴らしいパフォーマンスアップとなった。
※ESP32(160MHz)は、13秒みたいだが、これは、他にバリアとなる事象があるように思う
※このような有益な情報を提供してくれた「fujita nozomu」氏に感謝したい!

全ソースコードは以前から公開しているが、コンパイル環境を作り、フレームワークなどをチェックアウトする必要があるので、実行バイナリーも公開している。
・裏にあるスイッチを押す毎に、フルスクリーン(480×272)、レイのサンプリングを変更してのレンダリングを行う。

「raytracer.hpp」は、かなり柔軟性があり、ベンチマークには最適なので、PIC32や、他のマイコンでも十分実行できる、別のマイコンで試した人がいれば、是非情報を公開して欲しい~