ダブルバッファ
RX72N では、内蔵メモリが 512K + 512K と増えたので、480x272 の 16 ビットカラーで、ダブルバッファを使う事が出来る。
ダブルバッファは、デジタルストレージオシロスコープで波形をレンダリングするのに都合が良いので、実験する必要性があった。
RX65N では、フレームバッファが1枚分しかなく、16 ビットカラーでは、ダブルバッファにする事が出来ない。
※ 8 ビットカラーなら可能かもしれないが、ラインアドレスは 64 バイトの倍数にする制約があり変則的な設定となる。
描画オブジェクトが GUI のような性質ならまだやりようもあるが、ダイナミックな描画をしようとすると、どうしても、ダブルバッファが必要となる。
※描画速度を気にしなければ、他にも方法はある・・
ダブルバッファの描画では、毎フレーム以下の手順で描画を行う。
- 表示フレーム同期
- B のバッファを表示用にする
- A のバッファを全面クリア
- A のバッファに全てのオブジェクトを描画
- A、B を入れ替える
簡単には以上の手順を繰り返す。
通常、全面クリア+全てのオブジェクト描画が、1フレーム(16.6ミリ秒)以内に収まれば、フルフレーム(60Hz)で遅延なくスムーズに描画出来る。
描画オブジェクトが物理的に増えて、1フレームに収まらないと、フレームレートはどんどん落ちていく。
それでも、描画は裏で行っており、見えないので、チラツキは出ない。
glcdc_mgr は、フレームバッファの先頭アドレスを管理している。
GLCDC ハードウェアーは、水平ラインのアドレスは64の倍数でなければならない・・
ダブルバッファが有効な時、ページフリップに従ったアドレスを戻す。
static const int16_t line_width =
(((width * static_cast<int16_t>(PXT) / 8) + 63) & 0x7fc0) / (static_cast<int16_t>(PXT) / 8);
static const uint32_t frame_size =
line_width * (static_cast<uint32_t>(PXT) / 8) * height;
void* get_fbp() const noexcept
{
uint32_t ofs = 0;
if(enable_double_) {
ofs = (flip_count_ & 1) != 0 ? 0 : frame_size;
}
if(layer2_org_ != nullptr) {
uint32_t org = reinterpret_cast<uint32_t>(layer2_org_);
return reinterpret_cast<void*>(org + ofs);
} else if(layer1_org_ != nullptr) {
uint32_t org = reinterpret_cast<uint32_t>(layer1_org_);
return reinterpret_cast<void*>(org + ofs);
}
return nullptr;
}
垂直同期のタイミングで先頭アドレス(GR2FLM2)がロードされる。
※「GR2VEN」を有効にしておかないと、レジスタを書き換える事が出来ない。
void sync_vpos() const noexcept
{
if(enable_double_) {
uint32_t ofs = (flip_count_ & 1) != 0 ? 0 : frame_size;
uint32_t org = reinterpret_cast<uint32_t>(layer2_org_);
GLC::GR2VEN = 1;
GLC::GR2FLM2 = org + ofs;
}
volatile auto n = ctrl_blk_.vpos_count;
while(n == ctrl_blk_.vpos_count) {
asm("nop");
}
}
また、RX651/RX65N から搭載された描画エンジン DRW2D は、ソフトの描画に比べて、格段に高速なので、これを有効に活用するのに適している。
OpenGL 的なフレームワーク
「ダブルバッファ」と言えば、やはり3Dグラフィックスだろうと思う。
※スプライトと言う人もいるが・・・
昔から、リアルタイム 3D グラフィックスには、注力しているので、最も馴染みが深い OpenGL を縮小にした API を実装して実験してみた。
とりあえず、L チカに相当するキューブの回転w
RX マイコン内蔵の DRW2D 描画エンジンは、2D の描画用だが、基本的な構造が、3D にもマッチするように構成されている。
オブジェクトの座標をマトリックス演算して、透視変換、スクリーン座標変換まで行えば、2D 座標となり、そのまま描画出来る。
※テクスチャマッピングは多少難解かもしれないが、透視変換に適した API があるようだ。(これは今後の課題)
ポリゴンの描画では、裏と表の概念があり、親和性が良い。
※殆ど何もする事が無いと言いたいところだけど、立体ボリュームに対するクリッピング、ライティングなどを考えると、ちゃんと実装するのはかなり大変ではある。
端折ったテスト的なレンダリングなら簡単!
ほぼ OpenGL と同じような手順で描画出来るようにした、C++ なので、色々と便利機能を実装出来る。
float ax = 0.0f;
float ay = 0.0f;
while(1) {
render_.sync_frame();
render_.clear(DEF_COLOR::Black);
auto& m = tgl_.at_matrix();
m.set_viewport(0, 0, LCD_X, LCD_Y);
m.set_mode(gl::matrixf::mode::modelview);
m.identity();
m.translate(0.0f, 0.0f, -10.0f);
m.rotate(ax, 1.0f, 0.0f, 0.0f);
m.rotate(ay, 0.0f, 1.0f, 0.0f);
ax += 1.0f;
if(ax >= 360.0f) ax -= 360.0f;
ay += 1.5f;
if(ay >= 360.0f) ay -= 360.0f;
m.set_mode(gl::matrixf::mode::projection);
m.identity();
m.perspective(45.0f, static_cast<float>(LCD_X) / static_cast<float>(LCD_Y), 1.0f, 50.0f);
// draw_box_(2.0f, TGL::PTYPE::LINE_LOOP);
draw_box_(2.0f, TGL::PTYPE::QUAD);
tgl_.renderring();
}
RX72N TFU を有効に!
RX72N は、三角関数演算器を持っているので、これを有効に利用するコードを追加。
回転行列の演算では、以下の API を利用しているので、その部分で、専用 API を呼ぶようにした。
static inline void deg_sin_cos_(float deg, float& si, float& co) noexcept
{
#ifdef SIG_RX72N
__builtin_rx_sincosf(deg * vtx::deg2rad_f_, &si, &co);
#else
si = std::sin(deg * vtx::deg2rad_f_);
co = std::cos(deg * vtx::deg2rad_f_);
#endif
}
むすび
非常に初歩的ではあるが、3D オブジェクトの描画をテストした。
実用的に使うには、まだまだこれから色々入れないとならない・・・
とりあえず、ダブルバッファの実験から脱線したので、このプロジェクトはストールして、デジタルストレージオシロスコープの実装に戻る。
DRW2D エンジンは、今までほとんど使って来なかったので、良いきっかけになったと思う。
DRW2D エンジンは、アンチエリアスもサポートされており、このハードウェアーの情報は少ないが、かなり良く出来ていると思う。