--- nono/vm/renderer.cpp 2026/04/29 17:04:28 1.1.1.1 +++ nono/vm/renderer.cpp 2026/04/29 17:05:24 1.1.1.9 @@ -1,84 +1,116 @@ // // nono -// Copyright (C) 2018 isaki@NetBSD.org +// Copyright (C) 2020 nono project +// Licensed under nono-license.txt // -#include "object.h" -#include "bitmap.h" -#include "bt454.h" -#include "renderer.h" -#include "tvram.h" - -Renderer *gRenderer; +// +// レンダラ +// -static void *renderer_run(void *); +#include "renderer.h" +#include "planevram.h" +#include "syncer.h" +#include "uimessage.h" +#include "videoctlr.h" // コンストラクタ Renderer::Renderer() + : inherited(OBJ_RENDERER) { - logname = "renderer"; - devname = "Render"; +#if defined(HAVE_AVX2) + enable_avx2 = gMainApp.enable_avx2; +#endif + + monitor.func = ToMonitorCallback(&X68030Renderer::MonitorUpdate); + monitor.SetSize(68, 33); + monitor.Regist(ID_MONITOR_RENDERER); } // デストラクタ Renderer::~Renderer() { - // imagebuf の所有権は持っていないので解放不要。 - - // スレッド終了を指示 - std::unique_lock lock(mtx); - request |= REQ_TERMINATE; - cv.notify_one(); + TerminateThread(); } +// 初期化 bool Renderer::Init() { - // レンダリングスレッド起動 - pthread_t th; - pthread_create(&th, NULL, renderer_run, NULL); + if (inherited::Init() == false) { + return false; + } + + syncer = GetSyncer(); return true; } -bool +// リセット +void +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; +} + +// 電源オフ +void Renderer::PowerOff() { - if (imagebuf) { + if (enable) { // レンダリングスレッドに指示 std::unique_lock lock(mtx); request |= REQ_REND_POWEROFF; cv.notify_one(); } - - return true; } -// バッファとコールバックを指定する。 -// 引数 buf と callback はどちらも NULL であってはならない。 -// これは画面を必要とする wx 側から呼ばれる。CLI では呼ばれない。 +// 合成処理イネーブル +// 一度だけ呼び出すことができる void -Renderer::SetBuf(uint8 *buf, void (*callback)(void)) +Renderer::Enable() { - assert(buf); - assert(callback); + assert(enable == false); - imagebuf = buf; - refresh_callback = callback; + enable = true; + bitmap.Create(width, height); + // 初期値灰色にしておく + bitmap.Fill(Color(0x70, 0x70, 0x70)); } -// 画面合成処理 (CRTC とかから呼ばれる部分) +// 画面合成処理 (VM スレッドで CRTC/CRTC2 から呼ばれる)。 // 今は一画面単位で合成を行なっている。ラスター云々は未対応。 -// 画面合成を行うかどうかとか、レンダリングスレッドに指示を出すかとか -// どこら辺までを呼び出し元とこっちが分担するかは未定。 void Renderer::Render() { // CLI 版なら何もしない。 - if (imagebuf == NULL) { + 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 どっちのスレッドでやるか @@ -88,15 +120,12 @@ Renderer::Render() cv.notify_one(); } -// レンダリングスレッドのエントリポイント -void * -renderer_run(void *dummy) +// 全画面更新の指示 (パレット変更やスクロールなど VRAM の変更を伴わないもの)。 +// ここでは無効化だけ受け付けておいて、実際の処理はレンダリングの際に行う。 +void +Renderer::Invalidate2() { - pthread_setname_np("Renderer"); - pthread_detach(pthread_self()); - - gRenderer->ThreadRun(); - return NULL; + invalidate2 = true; } // レンダリングスレッド @@ -117,28 +146,159 @@ Renderer::ThreadRun() if ((req & REQ_TERMINATE)) { // 終了するので、他のフラグは無視 break; - - } else if ((req & REQ_REND_POWEROFF)) { + } + if ((req & REQ_REND_POWEROFF)) { // 電源オフ時の描画 (通常描画フラグは無視) // 電源オフされると画面を灰色にする。 // 黒でもいいけど、電源オンで黒いままなのと区別したい。 - memset(imagebuf, 0x70, width * height * BPP); - // ウィンドウに通知 - refresh_callback(); - - } else if ((req & REQ_REND_NORMAL)) { + bitmap.Fill(Color(0x70, 0x70, 0x70)); + // UI に通知 + UIMessage::Post(UIMessage::RENDER); + continue; + } + 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 (RenderMain()) { - // 更新があればウィンドウに通知 - refresh_callback(); + // レンダラを駆動する。 + // out_dirty は前回 UI に通知した後一度でも更新があれば true。 + render_time.Start(); + out_dirty |= RenderMain(); + render_time.Stop(); + render_exec++; + } + + 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; + } } } } } +// スレッドの終了を指示 +void +Renderer::Terminate() +{ + std::unique_lock lock(mtx); + request |= REQ_TERMINATE; + cv.notify_one(); +} + +// 描画更新(通知)間隔を設定 +void +Renderer::SetRefreshInterval(uint64 t) +{ + refresh_interval = t; +} + +// モニタ更新 +void +Renderer::MonitorUpdate(Monitor *, TextScreen& screen) +{ + int x; + int y; + + 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; + 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); + + 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); + } +} + +// 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)); + } + } +} + + // // X68030 レンダラ // @@ -149,6 +309,8 @@ X68030Renderer::X68030Renderer() // XXX とりあえず width = 768; height = 512; + + bitmap32.Create(width, height); } // デストラクタ @@ -156,16 +318,32 @@ X68030Renderer::~X68030Renderer() { } +// 初期化 +bool +X68030Renderer::Init() +{ + if (inherited::Init() == false) { + return false; + } + + planevram = GetPlaneVRAMDevice(); + videoctlr = GetVideoCtlrDevice(); + + return true; +} + // 画面合成 bool X68030Renderer::RenderMain() { - assert(imagebuf); - - // 今はテキスト画面のみを合成バッファに直接描いている - return gTVRAM->Render(imagebuf); + bool updated = videoctlr->Render(bitmap32, modify); + if (updated) { + RGBXtoRGB(bitmap, bitmap32); + } + return updated; } + // // LUNA レンダラ // @@ -175,6 +353,8 @@ LunaRenderer::LunaRenderer() { width = 1280; height = 1024; + + textbmp.Create(width, height); } // デストラクタ @@ -182,79 +362,52 @@ LunaRenderer::~LunaRenderer() { } +// 初期化 +bool +LunaRenderer::Init() +{ + if (inherited::Init() == false) { + return false; + } + + planevram = GetPlaneVRAMDevice(); + + return true; +} + // 画面合成 bool LunaRenderer::RenderMain() { - 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 = (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(textbmp, modify); + if (updated) { + RGBXtoRGB(bitmap, textbmp); + } + 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; - } - } +// +// 何もしないレンダラ +// - return updated; +// コンストラクタ +NopRenderer::NopRenderer() +{ + // XXX サポートしてない + width = 640; + height = 480; +} + +// デストラクタ +NopRenderer::~NopRenderer() +{ +} + +// 画面合成 +bool +NopRenderer::RenderMain() +{ + return false; }