Annotation of nono/vm/videoctlr.cpp, revision 1.1.1.11

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: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.