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