--- nono/vm/renderer.cpp 2026/04/29 17:04:36 1.1.1.3 +++ nono/vm/renderer.cpp 2026/04/29 17:05:32 1.1.1.11 @@ -4,113 +4,157 @@ // Licensed under nono-license.txt // -#include "object.h" -#include "bitmap.h" -#include "bt454.h" -#include "mythread.h" -#include "renderer.h" -#include "tvram.h" +// +// レンダラ +// -std::unique_ptr gRenderer; +#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 void *renderer_run(void *); +// 電源オフ時の画面色 (電源オンで黒なのとは区別したいのであえて灰色) +static const Color ScreenOffColor(0x70, 0x70, 0x70); // コンストラクタ Renderer::Renderer() + : inherited(OBJ_RENDERER) { - logname = "renderer"; - devname = "Render"; + monitor = gMonitorManager->Regist(ID_MONITOR_RENDERER, this); + monitor->func = ToMonitorCallback(&Renderer::MonitorUpdate); + monitor->SetSize(42, 10); } // デストラクタ Renderer::~Renderer() { - // imagebuf の所有権は持っていないので解放不要。 - - if (thread_created) { - Terminate(); - pthread_join(thread, NULL); - } + TerminateThread(); } +// 初期化 bool Renderer::Init() { - // レンダリングスレッド起動 - pthread_create(&thread, NULL, renderer_run, NULL); - thread_created = true; + if (inherited::Init() == false) { + return false; + } - return true; -} + scheduler = GetScheduler(); + syncer = GetSyncer(); -bool -Renderer::PowerOff() -{ - if (imagebuf) { - // レンダリングスレッドに指示 - std::unique_lock lock(mtx); - request |= REQ_REND_POWEROFF; - cv.notify_one(); + // GUI ならレンダラ有効。 + if (gMainApp.IsGUI()) { + enable = true; + bitmap.Create(width, height); + // 初期値 (どこでやる?) + bitmap.Fill(ScreenOffColor); } return true; } -// バッファとコールバックを指定する。 -// 引数 buf と callback はどちらも NULL であってはならない。 -// これは画面を必要とする wx 側から呼ばれる。CLI では呼ばれない。 +// リセット void -Renderer::SetBuf(uint8 *buf, void (*callback)(void)) +Renderer::ResetHard(bool poweron) { - assert(buf); - assert(callback); + next_refresh_rtime = syncer->GetRealTime(); - imagebuf = buf; - refresh_callback = callback; + // 統計情報をクリア + 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(); } -// 画面合成処理 (CRTC とかから呼ばれる部分) +// 電源オフ +void +Renderer::PowerOff() +{ + if (enable) { +#if defined(PERFREND) + sw_notify.Restart(); +#endif + // レンダリングスレッドに指示 + std::unique_lock lock(mtx); + request |= REQ_SCREENOFF; + cv.notify_one(); + } +} + +// 画面合成処理 (VM スレッドで呼ばれる)。 // 今は一画面単位で合成を行なっている。ラスター云々は未対応。 -// 画面合成を行うかどうかとか、レンダリングスレッドに指示を出すかとか -// どこら辺までを呼び出し元とこっちが分担するかは未定。 void -Renderer::Render() +Renderer::NotifyRender() { // CLI 版なら何もしない。 - if (imagebuf == NULL) { + if (enable == false) { return; } - // スケジューラ都合による間引きとかがあればここらへんで評価? - // XXX どっちのスレッドでやるか - - // レンダリングスレッドに指示 - std::unique_lock lock(mtx); - request |= REQ_REND_NORMAL; - cv.notify_one(); -} - -// レンダリングスレッドのエントリポイント -void * -renderer_run(void *dummy) -{ - PTHREAD_SETNAME("Renderer"); + // 統計情報 + stat_input_count++; + uint64 now = scheduler->GetVirtTime(); + ma_input_span.EnqueueForce(now - last_input_time); + last_input_time = now; + +#if defined(PERFREND) + sw_notify.Restart(); +#endif - gRenderer->ThreadRun(); - return NULL; + // レンダラスレッドに描画指示を出すだけ。 + { + 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; } @@ -118,39 +162,235 @@ Renderer::ThreadRun() if ((req & REQ_TERMINATE)) { // 終了するので、他のフラグは無視 break; + } - } else if ((req & REQ_REND_POWEROFF)) { - // 電源オフ時の描画 (通常描画フラグは無視) + // 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); + delay = 0; + } else if (t > 0) { + // 指定時間後に UI に通知。 + delay = t; + } + } +} - // 電源オフされると画面を灰色にする。 - // 黒でもいいけど、電源オンで黒いままなのと区別したい。 - memset(imagebuf, 0x70, width * height * BPP); - // ウィンドウに通知 - refresh_callback(); - - } else if ((req & REQ_REND_NORMAL)) { - // 通常描画 - - // レンダラを駆動して - if (RenderMain()) { - // 更新があればウィンドウに通知 - refresh_callback(); - } +// 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 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(new BitmapRGB(new_viewwidth, new_viewheight)); + 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; } -// スレッドの終了を指示 (スレッドは動いていること) +// スレッドの終了を指示 void Renderer::Terminate() { - assert(thread_created); - std::unique_lock lock(mtx); request |= REQ_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) +{ + refresh_interval = t; +} + +// モニタ更新 +void +Renderer::MonitorUpdate(Monitor *, TextScreen& screen) +{ + int x; + int y; + + screen.Clear(); + + x = 16; + y = 0; + + 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++; + + 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()); + + 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++; +} + + // // X68030 レンダラ // @@ -161,6 +401,8 @@ X68030Renderer::X68030Renderer() // XXX とりあえず width = 768; height = 512; + + bitmap.Create(width, height); } // デストラクタ @@ -168,16 +410,28 @@ X68030Renderer::~X68030Renderer() { } -// 画面合成 +// 初期化 bool -X68030Renderer::RenderMain() +X68030Renderer::Init() { - assert(imagebuf); + if (inherited::Init() == false) { + return false; + } + + videoctlr = GetVideoCtlrDevice(); - // 今はテキスト画面のみを合成バッファに直接描いている - return gTVRAM->Render(imagebuf); + return true; +} + +// 画面合成 +bool +X68030Renderer::RenderMD() +{ + bool updated = videoctlr->Render(bitmap); + return updated; } + // // LUNA レンダラ // @@ -187,6 +441,8 @@ LunaRenderer::LunaRenderer() { width = 1280; height = 1024; + + bitmap.Create(width, height); } // デストラクタ @@ -194,79 +450,64 @@ LunaRenderer::~LunaRenderer() { } +// 初期化 +bool +LunaRenderer::Init() +{ + if (inherited::Init() == false) { + return false; + } + + planevram = GetPlaneVRAMDevice(); + + return true; +} + // 画面合成 bool -LunaRenderer::RenderMain() +LunaRenderer::RenderMD() { - const uint8 *pal; - const uint32 *plane0; - std::atomic *dirty; - int hscroll; - int vscroll; - uint8 *dst0; - bool updated; - - assert(imagebuf); - dst0 = imagebuf; - - // パレット取得 - pal = gBT454->GetHostPalette(); - - // ビットマップ - gBitmap->GetRFCNT(&hscroll, &vscroll); - const uint8 *mem = gBitmap->GetBuf(); - dirty = gBitmap->GetDirty(); - - updated = false; - - // dirty は VRAM への書き込みがあれば常に 1 にする。 - // レンダリング時に dirty が 1 なら 2 にしてレンダリング。 - // レンダリング終了時に dirty が 2 なら 0 に戻す。 - // これによりレンダリング中に VRAM への書き込みがあれば次回描画される。 - - // sy はメモリ上の Y 座標 - // dy はレンダラ画面の Y 座標 (RFCNT のラスタカウンタを考慮) - - // XXX とりあえず1プレーン - plane0 = (const uint32 *)mem; - int sy = vscroll; - for (int dy = 0; dy < height; dst0 += width * BPP, dy++, sy++) { - sy &= 0x3ff; - - if (atomic_cas_8(&dirty[sy], 1, 2) == 1) { - uint8 *dst = dst0; - // sx はメモリ上の X オフセット (ロングワード単位) - // dx はレンダラ画面の X オフセット (ロングワード単位) - // XXX 水平オフセットが仕様以上の時の挙動は未実装 - - int sx = (sy * 2048 + hscroll) / 32; - int shift = (hscroll & 31); - for (int dx = 0; dx < width / 32; dx++, sx++) { - uint32 p0 = plane0[sx]; - if (shift) { - p0 = (p0 << shift) | (plane0[sx + 1] >> (32 - shift)); - } + bool updated = planevram->Render(bitmap); + return updated; +} - // 1ロングワードの横 32bit が横 32dot に相当 - for (int j = 0; j < 32; j++) { - // 1bpp の時 P1,P2,P3 分はプルアップされて %1 に - // なっているっぽいので、実質 #14 か #15 ということ。 - uint c = 14; - // カラーコード(パレット番号)を作成 - if ((p0 & 0x80000000U)) c |= 1; - p0 <<= 1; - - *dst++ = pal[c * 4 + 0]; - *dst++ = pal[c * 4 + 1]; - *dst++ = pal[c * 4 + 2]; - } - } - // 2の時だけ0に戻す - atomic_cas_8(&dirty[sy], 2, 0); - updated = true; - } +// +// コンソールレンダラ +// + +// コンストラクタ +ConsoleRenderer::ConsoleRenderer() +{ + // 視認性のためフチを追加。 + width = 640 + Padding * 2; + height = 480 + Padding * 2; + + bitmap.Create(width, height); +} + +// デストラクタ +ConsoleRenderer::~ConsoleRenderer() +{ +} + +// 初期化 +bool +ConsoleRenderer::Init() +{ + if (inherited::Init() == false) { + return false; } + console = GetConsoleDevice(); + + return true; +} + +// 画面合成 +bool +ConsoleRenderer::RenderMD() +{ + bool updated = console->Render(bitmap); return updated; }