File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / renderer.cpp
Revision 1.1.1.16 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:59 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// ビデオレンダラ
//

#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 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);

// コンストラクタ
VideoRenderer::VideoRenderer()
	: inherited(OBJ_VIDEO_RENDERER)
{
	AddAlias("Renderer");

	monitor = gMonitorManager->Regist(ID_MONITOR_RENDERER, this);
	monitor->SetCallback(&VideoRenderer::MonitorScreen);
	monitor->SetSize(42, 10);
}

// デストラクタ
VideoRenderer::~VideoRenderer()
{
	TerminateThread();
}

// 初期化
bool
VideoRenderer::Init()
{
	syncer = GetSyncer();
	uimessage = gMainApp.GetUIMessage();

	// GUI ならレンダラ有効。
	if (gMainApp.IsGUI()) {
		enable = true;
		bitmap.Create(width, height);
		// 初期値 (どこでやる?)
		bitmap.Fill(ScreenOffColor);
	}

	return true;
}

// リセット
void
VideoRenderer::ResetHard(bool poweron)
{
	next_refresh_rtime = syncer->GetRealTime();

	// 統計情報をクリア
	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
VideoRenderer::PowerOff()
{
	if (enable) {
#if defined(PERFREND)
		sw_notify.Restart();
#endif
		// レンダリングスレッドに指示
		std::lock_guard<std::mutex> lock(mtx);
		request |= REQ_SCREENOFF;
		cv.notify_one();
	}
}

// 画面合成処理 (VM スレッドで呼ばれる)。
// 今は一画面単位で合成を行なっている。ラスター云々は未対応。
void
VideoRenderer::NotifyRender()
{
	// CLI 版なら何もしない。
	if (enable == false) {
		return;
	}

	// 統計情報
	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

	// レンダラスレッドに描画指示を出すだけ。
	{
		std::lock_guard<std::mutex> lock(mtx);
		request |= REQ_RENDER;
		cv.notify_one();
	}
}

// レンダリングスレッド
void
VideoRenderer::ThreadRun()
{
	if (enable) {
		SetThreadAffinityHint(AffinityClass::Heavy);
	}

	int64 delay = 0;
	for (;;) {
		uint32 req;

		// 何か起きるまで待つ
		{
			std::unique_lock<std::mutex> lock(mtx);
			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;
		}

		if ((req & REQ_TERMINATE)) {
			// 終了するので、他のフラグは無視
			break;
		}

		// 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;
		}
	}
}

// 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;

	if ((req & REQ_RENDER)) {
		// 通常描画

#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
VideoRenderer::Terminate()
{
	std::lock_guard<std::mutex> 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<std::mutex> lock(mtx);
	request |= REQ_RESIZE;
	cv.notify_one();
}

// 描画更新(通知)間隔を設定。[msec] で指定。
void
VideoRenderer::SetRefreshInterval(uint64 msec)
{
	refresh_interval = msec_to_tsec(msec);
}

// モニタ更新
void
VideoRenderer::MonitorScreen(Monitor *, TextScreen& screen)
{
	int x;
	int y;
	uint64 ma;

	screen.Clear();

	x = 16;
	y = 0;

	screen.Puts(0, y++, "<Statistics>");

	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++;

	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());
	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++;
}


//
// X68030 レンダラ
//

// コンストラクタ
X68030VideoRenderer::X68030VideoRenderer()
{
	// XXX とりあえず
	width = 768;
	height = 512;

	bitmap.Create(width, height);
}

// デストラクタ
X68030VideoRenderer::~X68030VideoRenderer()
{
}

// 初期化
bool
X68030VideoRenderer::Init()
{
	if (inherited::Init() == false) {
		return false;
	}

	videoctlr = GetVideoCtlrDevice();

	return true;
}

// 画面合成
bool
X68030VideoRenderer::RenderMD()
{
	bool updated = videoctlr->Render(bitmap);
	return updated;
}


//
// LUNA レンダラ
//

// コンストラクタ
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);
}

// デストラクタ
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;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.