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