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