--- nono/vm/renderer.cpp 2026/04/29 17:05:24 1.1.1.9 +++ nono/vm/renderer.cpp 2026/04/29 17:05:41 1.1.1.13 @@ -9,22 +9,36 @@ // #include "renderer.h" +#include "console.h" +#include "monitor.h" #include "planevram.h" +#include "scheduler.h" #include "syncer.h" #include "uimessage.h" #include "videoctlr.h" +#if 0 +#define PERFREND +#include "stopwatch.h" +static Stopwatch sw_notify; +static uint64 notify_time; +static uint notify_count; +static uint64 render_time; +static uint64 stretch_time; +static uint64 convert_time; +static uint convert_count; +#endif + +// 電源オフ時の画面色 (電源オンで黒なのとは区別したいのであえて灰色) +static const Color ScreenOffColor(0x70, 0x70, 0x70); + // コンストラクタ Renderer::Renderer() : inherited(OBJ_RENDERER) { -#if defined(HAVE_AVX2) - enable_avx2 = gMainApp.enable_avx2; -#endif - - monitor.func = ToMonitorCallback(&X68030Renderer::MonitorUpdate); - monitor.SetSize(68, 33); - monitor.Regist(ID_MONITOR_RENDERER); + monitor = gMonitorManager->Regist(ID_MONITOR_RENDERER, this); + monitor->func = ToMonitorCallback(&Renderer::MonitorUpdate); + monitor->SetSize(42, 10); } // デストラクタ @@ -37,12 +51,17 @@ Renderer::~Renderer() bool Renderer::Init() { - if (inherited::Init() == false) { - return false; - } - + scheduler = GetScheduler(); syncer = GetSyncer(); + // GUI ならレンダラ有効。 + if (gMainApp.IsGUI()) { + enable = true; + bitmap.Create(width, height); + // 初期値 (どこでやる?) + bitmap.Fill(ScreenOffColor); + } + return true; } @@ -53,11 +72,16 @@ Renderer::ResetHard(bool poweron) 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(); } // 電源オフ @@ -65,80 +89,68 @@ void Renderer::PowerOff() { if (enable) { +#if defined(PERFREND) + sw_notify.Restart(); +#endif // レンダリングスレッドに指示 std::unique_lock lock(mtx); - request |= REQ_REND_POWEROFF; + 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() +Renderer::NotifyRender() { // CLI 版なら何もしない。 if (enable == false) { return; } - // 更新されていればレンダラに引き渡す - ModifyInfo mod = planevram->GetModify(); - if (__predict_false(invalidate2 == true)) { - mod.invalidate2 = true; - } - - if (mod.IsDirty() || mod.invalidate2) { - if (modq.Enqueue(mod)) { - // 更新情報を引き取ったのでクリアする - planevram->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::unique_lock lock(mtx); + request |= REQ_RENDER; + cv.notify_one(); + } } // レンダリングスレッド void Renderer::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; } @@ -147,44 +159,150 @@ Renderer::ThreadRun() // 終了するので、他のフラグは無視 break; } - if ((req & REQ_REND_POWEROFF)) { - // 電源オフ時の描画 (通常描画フラグは無視) - // 電源オフされると画面を灰色にする。 - // 黒でもいいけど、電源オンで黒いままなのと区別したい。 - bitmap.Fill(Color(0x70, 0x70, 0x70)); - // UI に通知 + // req に基づいて処理。 + int64 t = DoRender(req); + 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); - continue; + 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++; - } + } +} - // レンダラを駆動する。 - // out_dirty は前回 UI に通知した後一度でも更新があれば true。 - render_time.Start(); - out_dirty |= RenderMain(); - render_time.Stop(); - render_exec++; - } +// req に基づいて描画する。 +// UI への描画通知を遅延させる時間を返す (即通知する場合は 0)。 +// UI に通知しない場合は -1 を返す。 +int64 +Renderer::DoRender(uint32 req) +{ + // 1. REND_NORMAL なら電源オン時の通常描画指示、 + // REND_POWEROFF なら電源オン→オフ遷移時の描画指示。 + // 2. REND_RESIZE なら valid = false にして、表示ビットマップをリサイズ。 + // 3. 必要なら bitmap を拡大縮小。 + // 4. bitmap24 に描画して valid = true; - if (out_dirty) { - uint64 rtime = syncer->GetRealTime(); - if (rtime >= next_refresh_rtime) { - UIMessage::Post(UIMessage::RENDER); - next_refresh_rtime = rtime + refresh_interval; - out_dirty = false; - } - } +#if defined(PERFREND) + Stopwatch sw; +#endif + bool updated = false; + + if ((req & REQ_RENDER)) { + // 通常描画 + +#if defined(PERFREND) + sw_notify.Stop(); + notify_time += sw_notify.Elapsed(); + notify_count++; + + sw.Restart(); +#endif + // レンダラを駆動する。 + updated = RenderMD(); +#if defined(PERFREND) + sw.Stop(); + render_time += sw.Elapsed(); +#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_time += sw.Elapsed(); +#endif + + // wxWidgets 用の RGBX→RGB 変換。 +#if defined(PERFREND) + sw.Restart(); +#endif + src_bitmap->ConvertToRGB(*bitmap24); + bitmap24_valid = true; +#if defined(PERFREND) + sw.Stop(); + convert_time += sw.Elapsed(); +#endif + +#if defined(PERFREND) + if (++convert_count % 100 == 0) { + printf("Notify %u, Render %u, Stretch %u, Convert %u [usec]\n", + (uint)(notify_time / notify_count / 1000U), + (uint)(render_time / stat_render_count / 1000U), + (uint)(stretch_time / convert_count / 1000U), + (uint)(convert_time / convert_count / 1000U)); } +#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; } // スレッドの終了を指示 @@ -196,6 +314,20 @@ Renderer::Terminate() cv.notify_one(); } +// リサイズ指示 +void +Renderer::ResizeView(uint width_, uint height_) +{ + assert(enable); + + new_viewwidth = width_; + new_viewheight = height_; + + std::unique_lock lock(mtx); + request |= REQ_RESIZE; + cv.notify_one(); +} + // 描画更新(通知)間隔を設定 void Renderer::SetRefreshInterval(uint64 t) @@ -212,90 +344,49 @@ Renderer::MonitorUpdate(Monitor *, TextS screen.Clear(); - // modify - screen.Print(0, 0, "Last Update Region"); - const uint8 *m = &modify.bits[0]; - for (int i = 0; i < 32; i++) { - char buf[33]; - uint8 c; - for (int j = 0; j < 32; j++) { - if (__predict_false(*m++ != 0)) { - c = '*'; - } else { - c = '.'; - } - buf[j] = c; - } - - buf[32] = '\0'; - screen.Print(0, 1 + i, "%4d:%s", i * 32, buf); - } - screen.Puts(0, 1, "Y="); - - 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()); + uint count = ma_input_span.Length(); + uint64 ma = 0; + for (uint i = 0; i < count; i++) { + ma += ma_input_span.Peek(i); + } + if (count != 0) { + ma /= count; + } + screen.Puts(0, y, "Input Frequency(VT)"); + 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); -} -// RGBX から RGB への変換。 -void -Renderer::RGBXtoRGB(BitmapRGB& dst, const BitmapRGBX& src) -{ - assert(dst.GetWidth() == src.GetWidth()); - assert(dst.GetHeight() == src.GetHeight()); - assert(src.GetWidth() % 4 == 0); - -#if defined(HAVE_AVX2) - if (__predict_true(enable_avx2)) { - RGBXtoRGB_avx2(dst, src); - } else -#endif - { - RGBXtoRGB_gen(dst, src); - } -} + y++; + 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()); -// RGBX から RGB への変換。(C++ 版) -/*static*/ void -Renderer::RGBXtoRGB_gen(BitmapRGB& dst, const BitmapRGBX& src) -{ - for (int y = 0, yend = src.GetHeight(); y < yend; y++) { - const uint32 *s = (const uint32 *)src.GetRowPtr(y); - uint32 *d = (uint32 *)dst.GetRowPtr(y); - - const uint32 *send = s + src.GetWidth(); - for (; s < send; ) { - // 4ピクセルずつ処理する - - uint32 c0 = htole32(*s++); - uint32 c1 = htole32(*s++); - uint32 c2 = htole32(*s++); - uint32 c3 = htole32(*s++); - - // c0 = $x0B0G0R0 - // c1 = $x1B1G1R1 - // c2 = $x2B2G2R2 - // c3 = $x3B3G3R3 - // - // d[0] = $00B0G0R0 | $R1000000 = $R1B0G0R0 - // d[1] = $0000B1G1 | $G2R20000 = $G2R2B1G1 - // d[2] = $000000B2 | $B3G3R300 = $B3G3R3B2 - - *d++ = le32toh((c0 ) | (c1 << 24)); - *d++ = le32toh((c1 >> 8) | (c2 << 16)); - *d++ = le32toh((c2 >> 16) | (c3 << 8)); - } - } + y++; + screen.Puts(0, y, "Output Count"); + screen.Print(x, y++, "%26s", format_number(stat_output_count).c_str()); + count = ma_output_span.Length(); + ma = 0; + for (uint i = 0; i < count; i++) { + ma += ma_output_span.Peek(i); + } + if (count != 0) { + ma /= count; + } + screen.Puts(0, y, "Output Frequency(RT)"); + screen.Print(x + 18, y, "%5.1f Hz", + ma == 0 ? 0.0 : (double)1000'000'000 / ma); + y++; } @@ -310,7 +401,7 @@ X68030Renderer::X68030Renderer() width = 768; height = 512; - bitmap32.Create(width, height); + bitmap.Create(width, height); } // デストラクタ @@ -326,7 +417,6 @@ X68030Renderer::Init() return false; } - planevram = GetPlaneVRAMDevice(); videoctlr = GetVideoCtlrDevice(); return true; @@ -334,12 +424,9 @@ X68030Renderer::Init() // 画面合成 bool -X68030Renderer::RenderMain() +X68030Renderer::RenderMD() { - bool updated = videoctlr->Render(bitmap32, modify); - if (updated) { - RGBXtoRGB(bitmap, bitmap32); - } + bool updated = videoctlr->Render(bitmap); return updated; } @@ -354,7 +441,7 @@ LunaRenderer::LunaRenderer() width = 1280; height = 1024; - textbmp.Create(width, height); + bitmap.Create(width, height); } // デストラクタ @@ -377,37 +464,49 @@ LunaRenderer::Init() // 画面合成 bool -LunaRenderer::RenderMain() +LunaRenderer::RenderMD() { - // 型変換だけ必要 - bool updated = planevram->Render(textbmp, modify); - if (updated) { - RGBXtoRGB(bitmap, textbmp); - } + bool updated = planevram->Render(bitmap); return updated; } // -// 何もしないレンダラ +// コンソールレンダラ // // コンストラクタ -NopRenderer::NopRenderer() +ConsoleRenderer::ConsoleRenderer() { - // XXX サポートしてない - width = 640; - height = 480; + // 視認性のためフチを追加。 + width = 640 + Padding * 2; + height = 480 + Padding * 2; + + bitmap.Create(width, height); } // デストラクタ -NopRenderer::~NopRenderer() +ConsoleRenderer::~ConsoleRenderer() +{ +} + +// 初期化 +bool +ConsoleRenderer::Init() { + if (inherited::Init() == false) { + return false; + } + + console = GetConsoleDevice(); + + return true; } // 画面合成 bool -NopRenderer::RenderMain() +ConsoleRenderer::RenderMD() { - return false; + bool updated = console->Render(bitmap); + return updated; }