--- nono/vm/renderer.cpp 2026/04/29 17:05:13 1.1.1.7 +++ nono/vm/renderer.cpp 2026/04/29 17:05:59 1.1.1.16 @@ -5,126 +5,158 @@ // // -// レンダラ +// ビデオレンダラ // #include "renderer.h" -#include "lunafb.h" -#include "sync.h" +#include "console.h" +#include "monitor.h" +#include "planevram.h" +#include "scheduler.h" +#include "syncer.h" #include "uimessage.h" +#include "videoctlr.h" -// グローバル参照用 -Renderer *gRenderer; +#if 0 +#define PERFREND +#include "stopwatch.h" +static Stopwatch sw_notify; +static uint64 last_report_rtime; +static uint64 notify_nsec; +static uint notify_count; +static uint64 render_nsec; +static uint64 stretch_nsec; +static uint64 convert_nsec; +static uint convert_count; +#endif + +// 電源オフ時の画面色 (電源オンで黒なのとは区別したいのであえて灰色) +static const Color ScreenOffColor(0x70, 0x70, 0x70); // コンストラクタ -Renderer::Renderer() - : inherited("Renderer") +VideoRenderer::VideoRenderer() + : inherited(OBJ_VIDEO_RENDERER) { - monitor.func = ToMonitorCallback(&X68030Renderer::MonitorUpdate); - monitor.SetSize(68, 33); - monitor.Regist(ID_MONITOR_RENDERER); + AddAlias("Renderer"); + + monitor = gMonitorManager->Regist(ID_MONITOR_RENDERER, this); + monitor->SetCallback(&VideoRenderer::MonitorScreen); + monitor->SetSize(42, 10); } // デストラクタ -Renderer::~Renderer() +VideoRenderer::~VideoRenderer() { TerminateThread(); - gRenderer = NULL; +} + +// 初期化 +bool +VideoRenderer::Init() +{ + syncer = GetSyncer(); + uimessage = gMainApp.GetUIMessage(); + + // GUI ならレンダラ有効。 + if (gMainApp.IsGUI()) { + enable = true; + bitmap.Create(width, height); + // 初期値 (どこでやる?) + bitmap.Fill(ScreenOffColor); + } + + return true; } // リセット void -Renderer::ResetHard(bool poweron) +VideoRenderer::ResetHard(bool poweron) { - next_refresh_rtime = gSync->GetRealTime(); + next_refresh_rtime = syncer->GetRealTime(); // 統計情報をクリア - render_time.Reset(); - render_exec = 0; - last_mod_blocks = 0; - total_mod_blocks = 0; - total_invalidate2 = 0; + uint64 now = scheduler->GetVirtTime(); + stat_input_count = 0; + last_input_time = now; + ma_input_span.Clear(); + stat_render_count = 0; + stat_noupdate_count = 0; + stat_nobitmap_count = 0; + stat_output_count = 0; + last_output_time = now; + ma_output_span.Clear(); +#if defined(PERFREND) + last_report_rtime = syncer->GetRealTime(); +#endif } // 電源オフ void -Renderer::PowerOff() +VideoRenderer::PowerOff() { if (enable) { +#if defined(PERFREND) + sw_notify.Restart(); +#endif // レンダリングスレッドに指示 - std::unique_lock lock(mtx); - request |= REQ_REND_POWEROFF; + std::lock_guard lock(mtx); + request |= REQ_SCREENOFF; cv.notify_one(); } } -// 合成処理イネーブル -// 一度だけ呼び出すことができる -void -Renderer::Enable() -{ - assert(enable == false); - - enable = true; - bitmap.Create(width, height); - // 初期値灰色にしておく - bitmap.Fill(Color(0x70, 0x70, 0x70)); -} - -// 画面合成処理 (VM スレッドで CRTC/CRTC2 から呼ばれる)。 +// 画面合成処理 (VM スレッドで呼ばれる)。 // 今は一画面単位で合成を行なっている。ラスター云々は未対応。 void -Renderer::Render() +VideoRenderer::NotifyRender() { // CLI 版なら何もしない。 if (enable == false) { return; } - // 更新されていればレンダラに引き渡す - ModifyInfo mod = gPlaneVRAM->GetModify(); - if (__predict_false(invalidate2 == true)) { - mod.invalidate2 = true; - } - - if (mod.IsDirty() || mod.invalidate2) { - if (modq.Enqueue(mod)) { - // 更新情報を引き取ったのでクリアする - gPlaneVRAM->ClearDirty(); - invalidate2 = false; - } else { - // VM スレッドが速すぎるため次回に持ち越し - } - } - - // スケジューラ都合による間引きとかがあればここらへんで評価? - // XXX どっちのスレッドでやるか + // 統計情報 + stat_input_count++; + uint64 now = scheduler->GetVirtTime(); + ma_input_span.EnqueueForce(now - last_input_time); + last_input_time = now; - // レンダリングスレッドに指示 - std::unique_lock lock(mtx); - request |= REQ_REND_NORMAL; - cv.notify_one(); -} +#if defined(PERFREND) + sw_notify.Restart(); +#endif -// 全画面更新の指示 (パレット変更やスクロールなど VRAM の変更を伴わないもの)。 -// ここでは無効化だけ受け付けておいて、実際の処理はレンダリングの際に行う。 -void -Renderer::Invalidate2() -{ - invalidate2 = true; + // レンダラスレッドに描画指示を出すだけ。 + { + std::lock_guard lock(mtx); + request |= REQ_RENDER; + cv.notify_one(); + } } // レンダリングスレッド void -Renderer::ThreadRun() +VideoRenderer::ThreadRun() { + if (enable) { + SetThreadAffinityHint(AffinityClass::Heavy); + } + + int64 delay = 0; for (;;) { uint32 req; // 何か起きるまで待つ { std::unique_lock lock(mtx); - cv.wait(lock, [&] { return (request != 0); }); + if (delay == 0) { + cv.wait(lock, [&] { return (request != 0); }); + } else { + cv.wait_for(lock, std::chrono::nanoseconds(delay), + [&] { return (request != 0); }); + if (request == 0) { + request = REQ_POST; + } + } req = request; request = 0; } @@ -133,135 +165,253 @@ Renderer::ThreadRun() // 終了するので、他のフラグは無視 break; } - if ((req & REQ_REND_POWEROFF)) { - // 電源オフ時の描画 (通常描画フラグは無視) - // 電源オフされると画面を灰色にする。 - // 黒でもいいけど、電源オンで黒いままなのと区別したい。 - bitmap.Fill(Color(0x70, 0x70, 0x70)); - // UI に通知 - UIMessage::Post(UIMessage::RENDER); - continue; + // req に基づいて処理。 + int64 t = DoRender(req); +#if defined(PERFREND) + PrintPerf(); +#endif + if (t == 0 || (req & REQ_POST)) { + // UI に即通知。 + + // 統計情報 + stat_output_count++; + uint64 now = syncer->GetRealTime(); + ma_output_span.EnqueueForce(now - last_output_time); + last_output_time = now; + + uimessage->Post(UIMessage::RENDER); + delay = 0; + } else if (t > 0) { + // 指定時間後に UI に通知。 + delay = t; } - if ((req & REQ_REND_NORMAL)) { - // 通常描画 - if (modq.Dequeue(&modify)) { - // 更新ブロック数を数える (統計用) - last_mod_blocks = modify.n_dirty; - total_mod_blocks += last_mod_blocks; - if (__predict_false(modify.invalidate2)) { - total_invalidate2++; - } + } +} -#if defined(PERF_HIT) - // ブロックのヒット効率 - int update_word = 0; - for (auto v : modify.dirty_word) { - if (v) { - update_word++; - } - } - int update_block = last_mod_blocks * BLKY * (BLKX / 32); - hitrate_total_word += update_word; - hitrate_total_block += update_block; - hitrate_ma.EnqueueForce((double)update_word / update_block); +// req に基づいて描画する。 +// UI への描画通知を遅延させる時間を返す (即通知する場合は 0)。 +// UI に通知しない場合は -1 を返す。 +int64 +VideoRenderer::DoRender(uint32 req) +{ + // 1. REND_NORMAL なら電源オン時の通常描画指示、 + // REND_POWEROFF なら電源オン→オフ遷移時の描画指示。 + // 2. REND_RESIZE なら valid = false にして、表示ビットマップをリサイズ。 + // 3. 必要なら bitmap を拡大縮小。 + // 4. bitmap24 に描画して valid = true; + +#if defined(PERFREND) + Stopwatch sw; #endif + bool updated = false; - // レンダラを駆動する。 - // out_dirty は前回 UI に通知した後一度でも更新があれば true。 - render_time.Start(); - out_dirty |= RenderMain(); - render_time.Stop(); - render_exec++; - } + if ((req & REQ_RENDER)) { + // 通常描画 - if (out_dirty) { - uint64 rtime = gSync->GetRealTime(); - if (rtime >= next_refresh_rtime) { - UIMessage::Post(UIMessage::RENDER); - next_refresh_rtime = rtime + refresh_interval; - out_dirty = false; - } - } +#if defined(PERFREND) + sw_notify.Stop(); + notify_nsec += sw_notify.Elapsed_nsec(); + notify_count++; + + sw.Restart(); +#endif + // レンダラを駆動する。 + updated = RenderMD(); +#if defined(PERFREND) + sw.Stop(); + render_nsec += sw.Elapsed_nsec(); +#endif + stat_render_count++; + } + if ((req & REQ_SCREENOFF)) { + // 電源オン→オフ時の描画 + bitmap.Fill(ScreenOffColor); + updated = true; + } + + if ((req & REQ_RESIZE)) { + // 表示ビットマップのリサイズ要求 + + bitmap24_valid = false; + bitmap24.reset(); + try { + bitmap24.reset(new BitmapRGB(new_viewwidth, new_viewheight)); + } catch (...) { } + if ((bool)bitmap24 == false) { + putlog(0, "Could not allocate BitmapRGB(%u, %u)", + new_viewwidth, new_viewheight); + return -1; } + viewrect = Rect(0, 0, new_viewwidth, new_viewheight); + updated = true; } + + // 更新箇所がなければここで終わり。 + if (updated == false) { + stat_noupdate_count++; + return -1; + } + + // 出力ビットマップが用意できてなければここで終わり。 + if (__predict_false((bool)bitmap24 == false)) { + stat_nobitmap_count++; + return -1; + } + + // 等倍表示でなければ拡大縮小。 +#if defined(PERFREND) + sw.Restart(); +#endif + BitmapRGBX scaled_bitmap; + BitmapRGBX *src_bitmap; + if (bitmap.GetWidth() == bitmap24->GetWidth() && + bitmap.GetHeight() == bitmap24->GetHeight()) + { + src_bitmap = &bitmap; + } else { + scaled_bitmap.Create(viewrect.w, viewrect.h); + scaled_bitmap.DrawBitmapStretch(viewrect, bitmap); + src_bitmap = &scaled_bitmap; + } +#if defined(PERFREND) + sw.Stop(); + stretch_nsec += sw.Elapsed_nsec(); +#endif + + // wxWidgets 用の RGBX→RGB 変換。 +#if defined(PERFREND) + sw.Restart(); +#endif + src_bitmap->ConvertToRGB(*bitmap24); + bitmap24_valid = true; +#if defined(PERFREND) + sw.Stop(); + convert_nsec += sw.Elapsed_nsec(); + convert_count++; +#endif + + if ((req & (REQ_SCREENOFF | REQ_RESIZE))) { + // 電源オン→オフ遷移かリサイズ要求時は即通知。 + return 0; + } + + // 通常描画のみならここで通知を律速する。 + uint64 rtime = syncer->GetRealTime(); + if (rtime < next_refresh_rtime) { + return next_refresh_rtime - rtime; + } + next_refresh_rtime = rtime + refresh_interval; + return 0; } +#if defined(PERFREND) +void +VideoRenderer::PrintPerf() +{ + uint64 now = syncer->GetRealTime(); + if (now - last_report_rtime < 5_sec) { + return; + } + + double rate = 0; + uint nt_usec = 0; + uint rt_usec = 0; + uint st_usec = 0; + uint ct_usec = 0; + if (notify_count != 0) { + rate = (double)notify_count * 1_sec / (now - last_report_rtime); + nt_usec = nsec_to_usec(notify_nsec) / notify_count; + rt_usec = nsec_to_usec(render_nsec) / notify_count; + } + if (convert_count != 0) { + st_usec = nsec_to_usec(stretch_nsec) / convert_count; + ct_usec = nsec_to_usec(convert_nsec) / convert_count; + } + + printf( + "%5.1f calls/s: Notify %u us, Render %u us, Stretch %u us, Convert %u us\n", + rate, nt_usec, rt_usec, st_usec, ct_usec); + + notify_nsec = 0; + render_nsec = 0; + stretch_nsec = 0; + convert_nsec = 0; + notify_count = 0; + convert_count = 0; + last_report_rtime = now; +} +#endif // PERFREND + // スレッドの終了を指示 void -Renderer::Terminate() +VideoRenderer::Terminate() { - std::unique_lock lock(mtx); + std::lock_guard lock(mtx); request |= REQ_TERMINATE; cv.notify_one(); } -// 描画更新(通知)間隔を設定 +// リサイズ指示 +void +VideoRenderer::ResizeView(uint width_, uint height_) +{ + assert(enable); + + new_viewwidth = width_; + new_viewheight = height_; + + std::lock_guard lock(mtx); + request |= REQ_RESIZE; + cv.notify_one(); +} + +// 描画更新(通知)間隔を設定。[msec] で指定。 void -Renderer::SetRefreshInterval(uint64 t) +VideoRenderer::SetRefreshInterval(uint64 msec) { - refresh_interval = t; + refresh_interval = msec_to_tsec(msec); } // モニタ更新 void -Renderer::MonitorUpdate(Monitor *, TextScreen& screen) +VideoRenderer::MonitorScreen(Monitor *, TextScreen& screen) { int x; int y; + uint64 ma; screen.Clear(); - // modify - screen.Print(0, 0, "Last Update Region"); - for (int i = 0, end = modify.bits.size(); i < end; i++) { - char buf[17]; - - uint16 m = modify.bits[i]; - int j; - for (j = 0; j < 16; j++, m <<= 1) { - uint8 c; - if (__predict_false((int16)m < 0)) { - c = '*'; - } else { - c = '.'; - } - buf[j] = c; - } - buf[j] = '\0'; - - screen.Print((i / 32) * 21, 1 + (i % 32), "%2d:%s", i, buf); - } - - x = 43; + x = 16; y = 0; - uint64 nsec = render_time.Elapsed(); - screen.Print(x, y++, "RenderTime %3d.%06d sec", - (int)(nsec / 1000 / 1000 / 1000), - (int)(nsec / 1000) % (1000 * 1000)); - screen.Print(x, y++, "RenderExec %9" PRIu64, render_exec); + screen.Puts(0, y++, ""); + screen.Puts(0, y, "Input Count"); + screen.Print(x, y++, "%26s", format_number(stat_input_count).c_str()); + screen.Puts(0, y, "Input Frequency(VT)"); + ma = ma_input_span.GetMovingAverage(); + screen.Print(x + 18, y, "%5.1f Hz", + ma == 0 ? 0.0 : (double)1000'000'000 / ma); y++; - screen.Print(x, y++, "Last Mod Blks %9d", last_mod_blocks); - screen.Print(x, y++, "Total Mod Blks %9" PRIu64, total_mod_blocks); - screen.Print(x, y++, "Total Invalid2 %9" PRIu64, total_invalidate2); -#if defined(PERF_HIT) y++; - // ヒット効率(?)、移動平均 - double rate = 0.0; - int len = hitrate_ma.Length(); - for (int i = 0; i < len; i++) { - rate += hitrate_ma.Peek(i); - } - if (__predict_true(len != 0)) { - rate = rate / len * 100; - } - screen.Print(x, y++, "Hit Rate(MA) %5.1f%%", rate); - screen.Print(x, y++, " (total avg) %5.1f%%", - (double)hitrate_total_word / hitrate_total_block * 100); -#endif + screen.Puts(0, y, "RenderMD Count"); + screen.Print(x, y++, "%26s", format_number(stat_render_count).c_str()); + screen.Puts(0, y, "NoUpdate Count"); + screen.Print(x, y++, "%26s", format_number(stat_noupdate_count).c_str()); + screen.Puts(0, y, "NoBitmap Count"); + screen.Print(x, y++, "%26s", format_number(stat_nobitmap_count).c_str()); + + y++; + screen.Puts(0, y, "Output Count"); + screen.Print(x, y++, "%26s", format_number(stat_output_count).c_str()); + screen.Puts(0, y, "Output Frequency(RT)"); + ma = ma_output_span.GetMovingAverage(); + screen.Print(x + 18, y, "%5.1f Hz", + ma == 0 ? 0.0 : (double)1000'000'000 / ma); + y++; } @@ -270,24 +420,39 @@ Renderer::MonitorUpdate(Monitor *, TextS // // コンストラクタ -X68030Renderer::X68030Renderer() +X68030VideoRenderer::X68030VideoRenderer() { // XXX とりあえず width = 768; height = 512; + + bitmap.Create(width, height); } // デストラクタ -X68030Renderer::~X68030Renderer() +X68030VideoRenderer::~X68030VideoRenderer() { } +// 初期化 +bool +X68030VideoRenderer::Init() +{ + if (inherited::Init() == false) { + return false; + } + + videoctlr = GetVideoCtlrDevice(); + + return true; +} + // 画面合成 bool -X68030Renderer::RenderMain() +X68030VideoRenderer::RenderMD() { - // 今はテキスト画面のみを合成バッファに直接描いている - return gPlaneVRAM->Render(bitmap, modify); + bool updated = videoctlr->Render(bitmap); + return updated; } @@ -296,20 +461,77 @@ X68030Renderer::RenderMain() // // コンストラクタ -LunaRenderer::LunaRenderer() +LunaVideoRenderer::LunaVideoRenderer() { width = 1280; height = 1024; + + bitmap.Create(width, height); +} + +// デストラクタ +LunaVideoRenderer::~LunaVideoRenderer() +{ +} + +// 初期化 +bool +LunaVideoRenderer::Init() +{ + if (inherited::Init() == false) { + return false; + } + + planevram = GetPlaneVRAMDevice(); + + return true; +} + +// 画面合成 +bool +LunaVideoRenderer::RenderMD() +{ + bool updated = planevram->Render(bitmap); + return updated; +} + + +// +// コンソールレンダラ +// + +// コンストラクタ +ConsoleRenderer::ConsoleRenderer() +{ + // 視認性のためフチを追加。 + width = (80 * 8) + Padding * 2; + height = (24 * 16) + Padding * 2; + + bitmap.Create(width, height); } // デストラクタ -LunaRenderer::~LunaRenderer() +ConsoleRenderer::~ConsoleRenderer() +{ +} + +// 初期化 +bool +ConsoleRenderer::Init() { + if (inherited::Init() == false) { + return false; + } + + console = GetConsoleDevice(); + + return true; } // 画面合成 bool -LunaRenderer::RenderMain() +ConsoleRenderer::RenderMD() { - return gPlaneVRAM->Render(bitmap, modify); + bool updated = console->Render(bitmap); + return updated; }