|
|
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;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.