|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
5: //
6:
1.1.1.6 root 7: //
8: // ビデオコントローラ
9: //
1.1 root 10:
11: // e82000..e823ff パレット
12: // e82400..e824ff R0(e82400.w) のミラー
13: // e82500..e825ff R1(e82500.w) のミラー
14: // e82600..e826ff R2(e82600.w) のミラー
15: // e82700..e82fff $00 が読めるようだ
16: // 以上の 4KB をミラー。
17:
1.1.1.6 root 18: #include "videoctlr.h"
19: #include "mpu.h"
1.1.1.7 root 20: #include "renderer.h"
1.1.1.9 ! root 21: #include "scheduler.h"
1.1.1.7 root 22: #include "uimessage.h"
1.1.1.9 ! root 23: #include <cmath>
! 24:
! 25: // InsideOut p.135
! 26: static const busdata read_wait = busdata::Wait( 9 * 40_nsec);
! 27: static const busdata write_wait = busdata::Wait(11 * 40_nsec);
1.1 root 28:
1.1.1.6 root 29: // コンストラクタ
1.1 root 30: VideoCtlrDevice::VideoCtlrDevice()
1.1.1.8 root 31: : inherited(OBJ_VIDEOCTLR)
1.1 root 32: {
1.1.1.9 ! root 33: textbmp.Create(768, 512);
1.1.1.8 root 34: hostcolor.resize(16);
1.1.1.9 ! root 35:
! 36: #if defined(HAVE_AVX2)
! 37: enable_avx2 = gMainApp.enable_avx2;
! 38: #endif
! 39:
! 40: // 約30fps。これ以上速くても分からない。
! 41: contrast_event.func = ToEventCallback(&VideoCtlrDevice::ContrastCallback);
! 42: contrast_event.time = 30_msec;
! 43: contrast_event.Regist("VideoCtlr Analog Contrast");
1.1 root 44: }
45:
1.1.1.6 root 46: // デストラクタ
1.1 root 47: VideoCtlrDevice::~VideoCtlrDevice()
48: {
1.1.1.8 root 49: }
50:
51: // 初期化
52: bool
53: VideoCtlrDevice::Init()
54: {
55: if (inherited::Init() == false) {
56: return false;
57: }
58:
1.1.1.9 ! root 59: planevram = GetPlaneVRAMDevice();
1.1.1.8 root 60: renderer = GetRenderer();
61:
62: return true;
1.1 root 63: }
64:
1.1.1.6 root 65: // リセット
1.1.1.3 root 66: void
1.1.1.6 root 67: VideoCtlrDevice::ResetHard(bool poweron)
1.1.1.3 root 68: {
1.1.1.9 ! root 69: if (poweron) {
! 70: // 本当かどうかは知らないが、とりあえず。
! 71: // 初期状態は放電されていれば 0 のはずで、
! 72: // ビデオコントローラ側の出力の初期値はおそらく High のはず。
! 73: running_contrast = 0;
! 74: SetContrast(15);
! 75: }
1.1.1.3 root 76: }
77:
1.1.1.8 root 78: inline uint32
79: VideoCtlrDevice::Decoder(uint32 addr) const
80: {
81: return addr & 0xfff;
82: }
83:
1.1.1.9 ! root 84: busdata
1.1.1.8 root 85: VideoCtlrDevice::Read8(uint32 addr)
1.1 root 86: {
1.1.1.9 ! root 87: busdata data;
1.1.1.3 root 88:
1.1.1.8 root 89: uint32 offset = Decoder(addr);
90: if (offset < 0x400) { // パレット
91: data = palette[HB(offset)];
1.1.1.9 ! root 92: putlog(3, "Palette $%06x.B -> $%02x", addr, data.Data());
1.1.1.8 root 93:
94: } else if (offset < 0x700) { // R0,R1,R2
95: uint nr = Offset2NR(offset);
96: data = GetNR(nr);
97: putlog(3, "R%d.%c -> $%02x",
98: (nr / 2),
99: (nr % 2) == 0 ? 'H' : 'L',
1.1.1.9 ! root 100: data.Data());
1.1 root 101:
1.1.1.8 root 102: } else {
103: data = 0;
104: }
1.1 root 105:
1.1.1.9 ! root 106: data |= read_wait;
1.1.1.8 root 107: return data;
108: }
109:
1.1.1.9 ! root 110: busdata
1.1.1.8 root 111: VideoCtlrDevice::Read16(uint32 addr)
112: {
1.1.1.9 ! root 113: busdata data;
1.1 root 114:
1.1.1.8 root 115: uint32 offset = Decoder(addr);
116: if (offset < 0x400) { // パレット
117: data = *(uint16 *)&palette[offset];
1.1.1.9 ! root 118: putlog(3, "Palette $%06x.W -> $%04x", addr, data.Data());
1.1.1.8 root 119:
120: } else if (offset < 0x700) { // R0,R1,R2
121: uint nr = Offset2NR(offset);
122: data = GetNR(nr) << 8;
123: data |= GetNR(nr + 1);
1.1.1.9 ! root 124: putlog(3, "R%d -> $%04x", (nr / 2), data.Data());
1.1.1.8 root 125:
126: } else {
1.1 root 127: data = 0;
128: }
129:
1.1.1.9 ! root 130: data |= read_wait;
1.1 root 131: return data;
132: }
133:
1.1.1.9 ! root 134: busdata
1.1.1.8 root 135: VideoCtlrDevice::Write8(uint32 addr, uint32 data)
1.1 root 136: {
1.1.1.8 root 137: uint32 offset = Decoder(addr);
138: if (offset < 0x400) { // パレット
139: putlog(2, "Palette $%06x.B <- $%02x", addr, data);
140: palette[HB(offset)] = data;
1.1.1.2 root 141: MakePalette();
1.1 root 142:
1.1.1.8 root 143: } else if (offset < 0x700) { // R0,R1,R2
144: uint nr = Offset2NR(offset);
145: putlog(2, "R%d.%c <- $%02x (NOT IMPLEMENTED)",
146: (nr / 2),
147: (nr % 2) == 0 ? 'H' : 'L',
148: data);
149: SetNR(nr, data);
1.1 root 150:
1.1.1.8 root 151: } else {
152: // たぶん何も起きない?
1.1 root 153: }
154:
1.1.1.9 ! root 155: return write_wait;
1.1 root 156: }
157:
1.1.1.9 ! root 158: busdata
1.1.1.8 root 159: VideoCtlrDevice::Write16(uint32 addr, uint32 data)
1.1 root 160: {
1.1.1.8 root 161: uint32 offset = Decoder(addr);
162: if (offset < 0x400) { // パレット
163: putlog(2, "Palette $%06x.W <- $%04x", addr, data);
164: *(uint16 *)&palette[offset] = data;
165: MakePalette();
1.1 root 166:
1.1.1.8 root 167: } else if (offset < 0x700) { // R0,R1,R2
168: uint nr = Offset2NR(offset);
169: putlog(3, "R%d <- $%04x (NOT IMPLEMENTED)", (nr / 2), data);
170: SetNR(nr, data >> 8);
171: SetNR(nr + 1, data & 0xff);
1.1 root 172:
1.1.1.8 root 173: } else {
174: // たぶん何も起きない?
175: }
1.1 root 176:
1.1.1.9 ! root 177: return write_wait;
1.1.1.8 root 178: }
1.1 root 179:
1.1.1.9 ! root 180: busdata
1.1.1.8 root 181: VideoCtlrDevice::Peek8(uint32 addr)
182: {
183: uint32 offset = Decoder(addr);
184: if (offset < 0x400) { // パレット
185: return palette[HB(offset)];
186:
187: } else if (offset < 0x700) { // R0,R1,R2
188: uint nr = Offset2NR(offset);
189: return GetNR(nr);
1.1 root 190:
1.1.1.8 root 191: } else {
192: return 0x00;
1.1 root 193: }
1.1.1.8 root 194: }
1.1 root 195:
1.1.1.9 ! root 196: bool
1.1.1.8 root 197: VideoCtlrDevice::Poke8(uint32 addr, uint32 data)
198: {
199: uint32 offset = Decoder(addr);
200: if (offset < 0x400) {
201: if ((int32)data >= 0) {
202: palette[HB(offset)] = data;
203: // Poke による編集でもパレットに反映させる
204: MakePalette();
205: }
1.1.1.9 ! root 206: return true;
1.1.1.8 root 207: }
208:
1.1.1.9 ! root 209: return false;
1.1 root 210: }
211:
1.1.1.8 root 212: // R0, R1, R2 のオフセットから内部仮想レジスタ番号 NR0-NR5 に変換する。
213: // offset は R0, R1, R2 の領域を指していること。
214: inline uint
215: VideoCtlrDevice::Offset2NR(uint32 offset) const
1.1 root 216: {
1.1.1.8 root 217: assert(0x400 <= offset && offset < 0x700);
218: return ((offset / 0x100) - 4) * 2 + (offset & 1);
1.1 root 219: }
220:
1.1.1.8 root 221: // 内部仮想レジスタ NR0-NR5 を読み出す。
222: uint32
223: VideoCtlrDevice::GetNR(uint reg) const
1.1 root 224: {
1.1.1.8 root 225: return nreg[reg];
1.1 root 226: }
227:
1.1.1.8 root 228: // 内部仮想レジスタ NR0-NR5 にバイトデータを書き込む。
1.1 root 229: void
1.1.1.8 root 230: VideoCtlrDevice::SetNR(uint reg, uint32 data)
1.1 root 231: {
1.1.1.8 root 232: // XXX まだ値を保存する以外何もしていない
233: assert(reg < 6);
234: switch (reg) {
235: case 0: // R0.H
236: nreg[reg] = 0;
237: break;
238: case 1: // R0.L
239: nreg[reg] = data & 0x03;
240: break;
241:
242: case 2: // R1.H
243: nreg[reg] = data & 0x3f;
244: break;
245: case 3: // R1.L
246: nreg[reg] = data & 0xff;
247: break;
248:
249: case 4: // R2.H
250: nreg[reg] = data & 0xff;
251: break;
252: case 5: // R2.L
253: nreg[reg] = data & 0x7f;
254: break;
255:
256: default:
257: __unreachable();
258: }
1.1 root 259: }
1.1.1.2 root 260:
1.1.1.9 ! root 261: // コントラストを設定する。
! 262: // 指定値は 0-15 だが、内部では 0-xff で保持。
! 263: void
! 264: VideoCtlrDevice::SetContrast(int contrast_)
! 265: {
! 266: target_contrast = contrast_ * 0x11;
! 267:
! 268: if (running_contrast != target_contrast) {
! 269: initial_contrast = running_contrast;
! 270: contrast_time0 = scheduler->GetVirtTime();
! 271: scheduler->RestartEvent(contrast_event);
! 272: }
! 273: }
! 274:
! 275: // コントラスト変化イベント。
1.1.1.2 root 276: //
1.1.1.9 ! root 277: // Human68k 電源オフ時などの画面フェードアウトは ROM や Human68k がソフト
! 278: // ウェアで時間をかけてコントラストを変えているのではなく、ハードウェアで
! 279: // 実装してある。(わざわざ?) RC 回路が入っているのでおそらく意図していると
! 280: // 思われ。電源オフ時は ROM ルーチンがシステムポートにコントラスト 0 を1回
! 281: // だけ書き込んでいる。
! 282: //
! 283: // 細かいことは無視して RC 回路の過渡現象の式だけ持ってきて使う。
! 284: //
! 285: // 下がる時は E * exp(-t / RC) で、
! 286: // E は開始時の値なので initial_contrast、原点は target_contrast とする。
! 287: //
! 288: // E |*
! 289: // |*
! 290: // | *
! 291: // | ***
! 292: // | ****
! 293: // 0 +---------
! 294: //
! 295: // 上がる時は E * (1 - exp(-t / RC)) で、
! 296: // この場合 E は定常値なので target_contrast、原点が initial_contrast となる。
! 297: //
! 298: // E | ****
! 299: // | ***
! 300: // | *
! 301: // |*
! 302: // |*
! 303: // 0 +---------
! 304: //
! 305: // 途中まで充電されてる状態から始まる過渡現象がこれと同じかどうかは知らない
! 306: // けど、とりあえず毎回 initial と target の差だけで動作する。
! 307: void
! 308: VideoCtlrDevice::ContrastCallback(Event& ev)
! 309: {
! 310: const float RC = 0.18; // 時定数 1.8[KΩ]*100[uF]
! 311:
! 312: if (running_contrast != target_contrast) {
! 313: // 下がる時は (initial - target) * exp(-t / RC)
! 314: // 上がる時は (target - initial) * (1 - exp(-t / RC))
! 315: uint64 now = scheduler->GetVirtTime();
! 316: float t = (float)(now - contrast_time0) / 1e9;
! 317: float e = std::exp(-t / RC);
! 318: float n;
! 319: if (initial_contrast > target_contrast) {
! 320: float v = (float)(initial_contrast - target_contrast);
! 321: n = target_contrast + v * e;
! 322: } else {
! 323: float v = (float)(target_contrast - initial_contrast);
! 324: n = initial_contrast + v * (1 - e);
! 325: }
! 326: uint32 new_contrast = (uint32)(n + 0.5);
! 327: putlog(2, "Contrast %x->%x: t=%g e=%g new=%x",
! 328: initial_contrast, target_contrast, t, e, new_contrast);
! 329:
! 330: if (new_contrast != running_contrast) {
! 331: running_contrast = new_contrast;
! 332:
! 333: // 強制再描画をレンダラに通知
! 334: renderer->Invalidate2();
! 335: }
! 336:
! 337: scheduler->RestartEvent(ev);
! 338: }
! 339: }
! 340:
1.1.1.2 root 341: void
342: VideoCtlrDevice::MakePalette()
343: {
344: const uint16 *p;
345:
346: // パレット前半 256 ワードはグラフィックパレット、
347: // テキストパレットは後半。
1.1.1.8 root 348: p = (uint16 *)&palette[512];
1.1.1.2 root 349:
1.1.1.6 root 350: // パレット情報から Color 形式の色データを作成
1.1.1.2 root 351: // X680x0 のパレットは %GGGGG'RRRRR'BBBBB'I で並んでいる。
352: for (int i = 0; i < 16; i++) {
353: int I = (p[i] & 1) << 2; // 輝度ビット
354: int R = (((p[i] >> 6) & 0x1f) << 3) + I;
355: int G = (((p[i] >> 11)) << 3) + I;
356: int B = (((p[i] >> 1) & 0x1f) << 3) + I;
1.1.1.8 root 357: hostcolor[i].r = R;
358: hostcolor[i].g = G;
359: hostcolor[i].b = B;
1.1.1.2 root 360: }
1.1.1.6 root 361:
1.1.1.7 root 362: // パレット変更が起きたことをレンダラに通知
1.1.1.8 root 363: renderer->Invalidate2();
1.1.1.7 root 364:
365: // UI にも通知
366: UIMessage::Post(UIMessage::PALETTE);
1.1.1.2 root 367: }
1.1.1.8 root 368:
369: // ゲストパレットを uint32 形式にしたものの一覧を返す。(GUI 用)
370: // XXX 今の所テキスト16色しか考えてない
371: const std::vector<uint32>
372: VideoCtlrDevice::GetIntPalette() const
373: {
374: std::vector<uint32> ipal;
375: const uint16 *p = (const uint16 *)&palette[0];
376:
377: for (int i = 0; i < 16; i++) {
378: ipal.push_back(p[256 + i]);
379: }
380: return ipal;
381: }
1.1.1.9 ! root 382:
! 383: // 画面合成。
! 384: // レンダリングスレッドから呼ばれる。
! 385: bool
! 386: VideoCtlrDevice::Render(BitmapRGBX& dst, ModifyInfo& modify)
! 387: {
! 388: // 今はテキスト画面しかない。
! 389: bool updated = planevram->Render(textbmp, modify);
! 390:
! 391: // 最後にコントラスト適用。
! 392: // アトミックじゃなくても大丈夫のはず。
! 393: if (__predict_false(rendering_contrast != running_contrast)) {
! 394: rendering_contrast = running_contrast;
! 395: updated = true;
! 396: }
! 397: if (__predict_false(updated)) {
! 398: RenderContrast(dst, textbmp);
! 399: updated = true;
! 400: }
! 401: return updated;
! 402: }
! 403:
! 404: #if 0
! 405: #define PERFCONT
! 406: #include "stopwatch.h"
! 407: static uint64 perf_total;
! 408: static int perf_count;
! 409: #endif
! 410:
! 411: // コントラストを適用。
! 412: void
! 413: VideoCtlrDevice::RenderContrast(BitmapRGBX& dst, const BitmapRGBX& src)
! 414: {
! 415: assert(dst.GetWidth() == src.GetWidth());
! 416: assert(dst.GetHeight() == src.GetHeight());
! 417: assert(src.GetWidth() % 4 == 0);
! 418:
! 419: uint32 contrast = rendering_contrast;
! 420: if (contrast == 255) {
! 421: dst.CopyFrom(&src);
! 422: } else {
! 423: #if defined(PERFCONT)
! 424: Stopwatch sw;
! 425: sw.Start();
! 426: #endif
! 427:
! 428: #if defined(HAVE_AVX2)
! 429: if (__predict_true(enable_avx2)) {
! 430: RenderContrast_avx2(dst, src, contrast);
! 431: } else
! 432: #endif
! 433: {
! 434: RenderContrast_gen(dst, src, contrast);
! 435: }
! 436:
! 437: #if defined(PERFCONT)
! 438: sw.Stop();
! 439: perf_total += sw.Elapsed();
! 440: if (++perf_count % 100 == 0) {
! 441: printf("%" PRIu64 "\n", perf_total / perf_count);
! 442: }
! 443: #endif
! 444: }
! 445: }
! 446:
! 447: // コントラスト (0-254) を適用。C++ 版
! 448: /*static*/ void
! 449: VideoCtlrDevice::RenderContrast_gen(BitmapRGBX& dst, const BitmapRGBX& src,
! 450: uint32 contrast)
! 451: {
! 452: std::array<uint8, 256> lut;
! 453:
! 454: for (int i = 0; i < lut.size(); i++) {
! 455: lut[i] = (i * contrast) >> 8;
! 456: }
! 457:
! 458: for (int y = 0, yend = src.GetHeight(); y < yend; y++) {
! 459: const uint32 *s = (const uint32 *)src.GetRowPtr(y);
! 460: uint32 *d = (uint32 *)dst.GetRowPtr(y);
! 461:
! 462: const uint32 *send = s + src.GetWidth();
! 463: for (; s < send; ) {
! 464: // 1ピクセルずつ処理する
! 465: // 445.1 usec
! 466:
! 467: uint32 c = htole32(*s++);
! 468:
! 469: uint32 r = lut[c & 0xff];
! 470: uint32 g = lut[(c >> 8) & 0xff];
! 471: uint32 b = lut[(c >> 16) & 0xff];
! 472:
! 473: c = r | (g << 8 ) | (b << 16);
! 474: *d++ = le32toh(c);
! 475: }
! 476: }
! 477: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.