--- nono/vm/videoctlr.cpp 2026/04/29 17:05:33 1.1.1.11 +++ nono/vm/videoctlr.cpp 2026/04/29 17:05:50 1.1.1.13 @@ -16,10 +16,13 @@ // 以上の 4KB をミラー。 #include "videoctlr.h" +#include "event.h" +#include "gvram.h" #include "mainapp.h" #include "planevram.h" #include "renderer.h" #include "scheduler.h" +#include "textscreen.h" #include "uimessage.h" #include @@ -31,8 +34,12 @@ static const busdata write_wait = busdat VideoCtlrDevice::VideoCtlrDevice() : inherited(OBJ_VIDEOCTLR) { - textbmp.Create(768, 512); - hostcolor.resize(16); + txbmp.Create(1024, 1024); + grbmp.Create(1024, 1024); + mixbmp.Create(1024, 1024); + + palette.resize(512); + hostcolor.resize(512); #if defined(HAVE_AVX2) enable_avx2 = gMainApp.enable_avx2; @@ -51,18 +58,17 @@ VideoCtlrDevice::~VideoCtlrDevice() bool VideoCtlrDevice::Init() { - if (inherited::Init() == false) { - return false; - } - + gvram = GetGVRAMDevice(); planevram = GetPlaneVRAMDevice(); renderer = GetRenderer(); + uimessage = gMainApp.GetUIMessage(); + auto evman = GetEventManager(); + contrast_event = evman->Regist(this, + ToEventCallback(&VideoCtlrDevice::ContrastCallback), + "VideoCtlr Analog Contrast"); // 約30fps。これ以上速くても分からない。 - contrast_event.func = ToEventCallback(&VideoCtlrDevice::ContrastCallback); - contrast_event.time = 30_msec; - contrast_event.SetName("VideoCtlr Analog Contrast"); - scheduler->RegistEvent(contrast_event); + contrast_event->time = 30_msec; return true; } @@ -78,6 +84,10 @@ VideoCtlrDevice::ResetHard(bool poweron) running_contrast = 0; SetContrast(15); } + + // リセット直後に再描画を起こすため、一致しない値にしておく。 + prev_reg1 = -1; + prev_reg2 = -1; } /*static*/ inline uint32 @@ -122,10 +132,10 @@ VideoCtlrDevice::Write(busaddr addr, uin } else if (offset < 0x700) { // R0,R1,R2 uint rn = Offset2Rn(offset); if (datasize == 1) { - putlogn("R%u.%c <- $%02x (NOT IMPLEMENTED)", + putlogn("R%u.%c <- $%02x", rn, ((offset & 1U) ? 'H' : 'L'), data); } else { - putlogn("R%u <- $%04x (NOT IMPLEMENTED)", rn, data); + putlogn("R%u <- $%04x", rn, data); } } } @@ -167,6 +177,152 @@ VideoCtlrDevice::Poke1(uint32 addr, uint return false; } +// モニタ更新の下請け。CRTC から呼ばれる。 +int +VideoCtlrDevice::MonitorUpdate(TextScreen& screen, int y, uint32 crtc_r20) +{ + std::array tmp = reg; + + for (uint i = 0; i < tmp.size(); i++) { + screen.Print(0, y + i, "$%06x R%u: $%04x", + baseaddr + 0x400 + i * 0x100, i, tmp[i]); + } + + // R0 (p.234) + // CRTC R20 の bit10-8 と同じでなければいけないと書いてあるが、 + // 違ったら CRTC のほうが使われてるっぽいので、こっちを赤とかにしてみる。 + // (NetBSD/x68k の X) + uint tmp0 = tmp[0] & 7; + uint diff = tmp0 ^ (crtc_r20 >> 8); + screen.Print(18, y, ((diff & R0_SIZ) ? TA::ReverseRed : TA::Normal), + "SIZ=%s", siz_str[tmp0 >> 2]); + screen.Print(32, y, ((diff & R0_COL) ? TA::ReverseRed : TA::Normal), + "COL=%s", col_str[tmp0 & R0_COL]); + // ちょっと間借りして内部情報を表示。 + { + char sobuf[32]; + int len = strlcpy(sobuf, "(SCREEN_ORDER=", sizeof(sobuf)); + for (uint32 s = screen_order; s != 0; s >>= 4) { + static const char so_str[] = ".STGBK"; + sobuf[len++] = so_str[s & 0xf]; + } + sobuf[len] = '\0'; + len = strlcat(sobuf, ")", sizeof(sobuf)); + screen.Puts(screen.GetCol() - len, y, sobuf); + } + y++; + + // R1 (p.188) + { + std::array plist {}; + uint sp = (tmp[1] >> 12) & 3; + uint tp = (tmp[1] >> 10) & 3; + uint gp = (tmp[1] >> 8) & 3; + plist[sp] = "SP"; + plist[tp] = "TX"; + plist[gp] = "GR"; + std::string pstr; + if (plist[3]) { + pstr = "Invalid"; + } else if (plist[0] == NULL || plist[1] == NULL || plist[2] == NULL) { + pstr = "Conflict"; + } else { + pstr = string_format("%s > %s > %s", plist[0], plist[1], plist[2]); + } + + uint gn = tmp[1] & 0xff; + std::string gstr; + switch (tmp0 & 3) { + case 0: // 16色モード、グラフィックページは4枚。 + { + std::array glist {}; + uint p3 = (tmp[1] >> 6) & 3; + uint p2 = (tmp[1] >> 4) & 3; + uint p1 = (tmp[1] >> 2) & 3; + uint p0 = tmp[1] & 3; + glist[p0] = "P0"; + glist[p1] = "P1"; + glist[p2] = "P2"; + glist[p3] = "P3"; + if (glist[0] && glist[1] && glist[2] && glist[3]) { + gstr = string_format("%s > %s > %s > %s", + glist[0], glist[1], glist[2], glist[3]); + } else { + gstr = "Conflict"; + } + break; + } + + case 1: // 256色モード、グラフィックページは2枚。 + // この場合 $e4 か $4e だけが有効。 + if (gn == 0xe4) { + gstr = "P0 > P1"; + } else if (gn == 0x4e) { + gstr = "P1 > P0"; + } else { + gstr = "Invalid"; + } + break; + + case 2: // 未定義 + gstr = "?"; + break; + + case 3: // 65536色モード、グラフィックページは1枚。 + gstr = "P0"; + break; + + default: + __unreachable(); + } + + // 2 3 4 5 6 + // 89012345678901234567890123456789012345678901 + // Priority=SP > TX > GR, GP=P0 > P1 > P2 > P3 + screen.Print(18, y, "Priority=%s,", pstr.c_str()); + screen.Print(42, y, "GP=%s", gstr.c_str()); + } + y++; + + // R2 (p.210) + uint x = 18; + uint tmp2 = tmp[2]; + screen.Print(x + 0, y, TA::OnOff(tmp2 & R2_YS), "YS"); + screen.Print(x + 3, y, TA::OnOff(tmp2 & R2_AH), "AH"); + screen.Print(x + 6, y, TA::OnOff(tmp2 & R2_VH), "VH"); + screen.Print(x + 9, y, TA::OnOff(tmp2 & R2_EX), "EX"); + screen.Print(x + 12, y, TA::OnOff(tmp2 & R2_HP), "HP"); + screen.Print(x + 15, y, TA::OnOff(tmp2 & R2_BP), "BP"); + screen.Print(x + 18, y, TA::OnOff(tmp2 & R2_GG), "GG"); + screen.Print(x + 21, y, TA::OnOff(tmp2 & R2_GT), "GT"); + screen.Print(x + 24, y, TA::OnOff(tmp2 & 0x0080), "0"); + screen.Print(x + 26, y, TA::OnOff(tmp2 & R2_SP_ON), "SP"); + screen.Print(x + 29, y, TA::OnOff(tmp2 & R2_TX_ON), "TX"); + screen.Print(x + 32, y, TA::OnOff(tmp2 & R2_GR_ON), "GR"); + screen.Print(x + 35, y, TA::OnOff(tmp2 & R2_G3_ON), "G3"); + screen.Print(x + 38, y, TA::OnOff(tmp2 & R2_G2_ON), "G2"); + screen.Print(x + 41, y, TA::OnOff(tmp2 & R2_G1_ON), "G1"); + screen.Print(x + 44, y, TA::OnOff(tmp2 & R2_G0_ON), "G0"); + y++; + + return y; +} + +// CRTC からも使う。 +/*static*/ const char * const +VideoCtlrDevice::siz_str[2] = { + "512x512", + "1024x1024", +}; + +/*static*/ const char * const +VideoCtlrDevice::col_str[4] = { + "16", + "256", + "undef", + "65536", +}; + // R0, R1, R2 のアドレスオフセットからレジスタ番号 0, 1, 2 を返す。 // offset は R0, R1, R2 の領域を指していること。 /*static*/ inline uint32 @@ -264,7 +420,7 @@ VideoCtlrDevice::SetContrast(uint contra // | * // | *** // | **** -// 0 +--------- +// 0 +---------> t // // 上がる時は E * (1 - exp(-t / RC)) で、 // この場合 E は定常値なので target_contrast、原点が initial_contrast となる。 @@ -274,12 +430,12 @@ VideoCtlrDevice::SetContrast(uint contra // | * // |* // |* -// 0 +--------- +// 0 +---------> t // // 途中まで充電されてる状態から始まる過渡現象がこれと同じかどうかは知らない // けど、とりあえず毎回 initial と target の差だけで動作する。 void -VideoCtlrDevice::ContrastCallback(Event& ev) +VideoCtlrDevice::ContrastCallback(Event *ev) { const float RC = 0.18; // 時定数 1.8[KΩ]*100[uF] @@ -312,19 +468,13 @@ VideoCtlrDevice::ContrastCallback(Event& void VideoCtlrDevice::MakePalette() { - const uint16 *p; - - // パレット前半 256 ワードはグラフィックパレット、 - // テキストパレットは後半。 - p = &palette[256]; - // パレット情報から Color 形式の色データを作成 // X680x0 のパレットは %GGGGG'RRRRR'BBBBB'I で並んでいる。 - for (int i = 0; i < 16; i++) { - uint I = (p[i] & 1) << 2; // 輝度ビット - uint R = (((p[i] >> 6) & 0x1f) << 3) + I; - uint G = (((p[i] >> 11)) << 3) + I; - uint B = (((p[i] >> 1) & 0x1f) << 3) + I; + for (int i = 0; i < palette.size(); i++) { + uint I = (palette[i] & 1) << 2; // 輝度ビット + uint R = (((palette[i] >> 6) & 0x1f) << 3) + I; + uint G = (((palette[i] >> 11)) << 3) + I; + uint B = (((palette[i] >> 1) & 0x1f) << 3) + I; hostcolor[i].r = R; hostcolor[i].g = G; hostcolor[i].b = B; @@ -335,21 +485,7 @@ VideoCtlrDevice::MakePalette() planevram->Invalidate2(); // UI にも通知 - UIMessage::Post(UIMessage::PALETTE); -} - -// ゲストパレットを uint32 形式にしたものの一覧を返す。(GUI 用) -// XXX 今の所テキスト16色しか考えてない -const std::vector -VideoCtlrDevice::GetIntPalette() const -{ - std::vector ipal; - const uint16 *p = &palette[256]; - - for (int i = 0; i < 16; i++) { - ipal.push_back(p[i]); - } - return ipal; + uimessage->Post(UIMessage::PALETTE); } // 画面の作成。CRTC から VDisp のタイミングで呼ばれる。 @@ -359,10 +495,54 @@ VideoCtlrDevice::VDisp() { bool update_needed = false; - // 今はテキスト画面しかない。 + // 各画面に更新があるか。モニタ用にもなるためオフでも行う。 if (planevram->Snap()) { update_needed = true; } + if (gvram->Snap()) { + update_needed = true; + } + + // プライオリティかオンオフが変わっていても再描画。 + if (reg[1] != prev_reg1 || (reg[2] & 0xff) != prev_reg2) { + std::array list {}; + // オンオフと描画順。 + // XXX グラフィック画面のプレーン(ページ)は未対応 +#if 0 + if ((reg[2] & R2_SP_ON)) { + uint sp = (reg[1] >> 12) & 3; + list[sp] = SORDER_SP; + } +#endif + if ((reg[2] & R2_TX_ON)) { + uint tp = (reg[1] >> 10) & 3; + list[tp] = SORDER_TX; + } + if ((reg[2] & R2_GR_ON)) { + uint gp = (reg[1] >> 8) & 3; + list[gp] = SORDER_GR; + } + + uint32 new_order = 0; + for (uint i = 0; i < 3; i++) { + if (list[i] != SORDER_NONE) { + new_order <<= 4; + new_order |= list[i]; + } + } + // 全レイヤーオフなら仮想的な黒画面を出力。 + if (new_order == 0) { + new_order = SORDER_BLACK; + } + // 変化があれば再描画。 + if (new_order != screen_order) { + screen_order = new_order; + update_needed = true; + } + + prev_reg1 = reg[1]; + prev_reg2 = reg[2] & 0xff; + } // コントラストが変わっていても再描画。 if (__predict_false(rendering_contrast != running_contrast)) { @@ -380,19 +560,49 @@ VideoCtlrDevice::VDisp() bool VideoCtlrDevice::Render(BitmapRGBX& dst) { - // 今はテキスト画面しかない。 - bool updated = planevram->Render(textbmp); + uint32 local_screen_order = screen_order; + + // 各画面を描画。各画面(レイヤー)はモニタ表示用としても使うため、 + // 表示オンオフに関わらず、常に作成は行う。 + bool tx_updated = planevram->Render(txbmp); + bool gr_updated = gvram->Render(grbmp); + + // mixbmp に合成。mixbmp もモニタ表示に使う。 + // XXX 重ね合わせはまだ + bool mix_updated = false; + for (uint32 s = local_screen_order; s != 0; s >>= 4) { + switch (s & 0xf) { + case SORDER_TX: + mixbmp.CopyFrom(&txbmp); + mix_updated = tx_updated; + break; + case SORDER_GR: + mixbmp.CopyFrom(&grbmp); + mix_updated = gr_updated; + break; + case SORDER_BLACK: + mixbmp.Fill(0); + mix_updated = true; + break; + case SORDER_SP: + case SORDER_BG: + default: + break; + } + } // 最後にコントラスト適用。 + bool updated = false; int contrast = pending_contrast.exchange(rendering_contrast); if (contrast != rendering_contrast) { rendering_contrast = contrast; updated = true; } - if (__predict_false(updated)) { - RenderContrast(dst, textbmp); + if (__predict_false(mix_updated || updated)) { + RenderContrast(dst, mixbmp); updated = true; } + return updated; } @@ -407,13 +617,15 @@ static uint perf_count; void VideoCtlrDevice::RenderContrast(BitmapRGBX& dst, const BitmapRGBX& src) { - assert(dst.GetWidth() == src.GetWidth()); - assert(dst.GetHeight() == src.GetHeight()); assert(src.GetWidth() % 4 == 0); uint32 contrast = rendering_contrast; - if (contrast == 255) { - dst.CopyFrom(&src); + if (__predict_false(contrast == 255)) { + Rect rect(0, 0, dst.GetWidth(), dst.GetHeight()); + dst.DrawBitmap(0, 0, src, rect); + } else if (__predict_false(contrast == 0)) { + uint8 *d = dst.GetBuf(); + memset(d, 0, dst.GetStride() * dst.GetHeight()); } else { #if defined(PERFCONT) Stopwatch sw; @@ -444,7 +656,7 @@ VideoCtlrDevice::RenderContrast(BitmapRG } } -// コントラスト (0-254) を適用。C++ 版 +// コントラスト (1-254) を適用。dst サイズでクリップする。C++ 版。 /*static*/ void VideoCtlrDevice::RenderContrast_gen(BitmapRGBX& dst, const BitmapRGBX& src, uint32 contrast) @@ -455,22 +667,19 @@ VideoCtlrDevice::RenderContrast_gen(Bitm lut[i] = (i * contrast) >> 8; } - // この変換は一次元配列とみなしても行える。 - // また必ずテキスト画面全域なので端数の考慮は不要。 - uint pxlen = src.GetWidth() * src.GetHeight(); - const uint32 *s32 = (const uint32 *)src.GetRowPtr(0); - const uint32 *s32end = s32 + pxlen; - uint32 *d32 = (uint32 *)dst.GetRowPtr(0); - - // 1ピクセルずつ処理する - for (; s32 < s32end; ) { - Color cs(*s32++); - - uint32 r = lut[cs.r]; - uint32 g = lut[cs.g]; - uint32 b = lut[cs.b]; + for (uint y = 0, yend = dst.GetHeight(); y < yend; y++) { + const uint32 *s32 = (const uint32 *)src.GetRowPtr(y); + uint32 *d32 = (uint32 *)dst.GetRowPtr(y); + for (uint x = 0, xend = dst.GetWidth(); x < xend; x++) { + // 1ピクセルずつ処理する。 + Color cs(*s32++); + + uint32 r = lut[cs.r]; + uint32 g = lut[cs.g]; + uint32 b = lut[cs.b]; - Color cd(r, g, b); - *d32++ = cd.u32; + Color cd(r, g, b); + *d32++ = cd.u32; + } } }