--- nono/vm/tvram.cpp 2026/04/29 17:04:42 1.1.1.5 +++ nono/vm/tvram.cpp 2026/04/29 17:05:18 1.1.1.9 @@ -8,62 +8,57 @@ // TVRAM // +// TVRAM (PlaneVRAM) はロングワード単位でホストバイトオーダ配置なので、 +// バイトアクセス時は HLB()、ワードアクセス時は HLW() マクロを使用のこと。 + #include "tvram.h" -#include "crtc.h" -#include "renderer.h" +#include "mpu.h" #include "videoctlr.h" -#if defined(__x86_64__) -#include -#endif - -#if defined(PERF) -#include "perfcounter.h" -static PerfCounter perf(100); -#endif - -// -// TVRAM はワード単位でホストバイトオーダ配置なので、 -// バイトアクセス時は HB() マクロを使用のこと。 -// - -std::unique_ptr gTVRAM; +// コンストラクタ TVRAMDevice::TVRAMDevice() + : inherited(1024, 1024) { - logname = "tvram"; - devname = "TVRAM"; - devaddr = baseaddr; - devlen = 512 * 1024; + SetName("TVRAM"); + ClearAlias(); + AddAlias("TVRAM"); - mem.reset(new uint8[devlen]); + uint devlen = 512 * 1024; - // レンダリング時に使うテーブルを事前に計算。 - // %abcdefgh の8ビットを - // %000h000g'000f000e'000d000c'000b000a - // の32ビットに伸張する。(各 nibble の最下位ビットに対応させる) - for (int i = 0; i < 256; i++) { - uint32_t res = 0; - for (int bit = 0; bit < 8; bit++) { - res |= ((i >> bit) & 1) << (28 - bit * 4); - } - deptable[i] = res; - } + // プレーン数は4枚固定 + nplane = NPLANE; + + mem.reset(new uint8[devlen]); } +// デストラクタ TVRAMDevice::~TVRAMDevice() { } +// 初期化 +bool +TVRAMDevice::Init() +{ + if (inherited::Init() == false) { + return false; + } + + renderer = GetRenderer(); + auto videoctlr = GetVideoCtlrDevice(); + + // 加工済みパレットを取得 + palette = &(videoctlr->GetHostPalette())[0]; + + return true; +} + +// リセット void -TVRAMDevice::ResetHard() +TVRAMDevice::ResetHard(bool poweron) { // XXX ここ? Invalidate(); - -#if defined(PERF) - // 分かりやすさのために表示 - printf("measuring TVRAM performance...\n"); -#endif } inline uint64 @@ -75,42 +70,56 @@ TVRAMDevice::Decoder(uint32 addr) const uint64 TVRAMDevice::Read8(uint32 addr) { - gMPU->AddCycle(9); // Inside/Out p.135 + mpu->AddCycle(9); // Inside/Out p.135 uint32 offset = Decoder(addr); - return mem[HB(offset)]; + return mem[HLB(offset)]; } uint64 TVRAMDevice::Read16(uint32 addr) { - gMPU->AddCycle(9); // Inside/Out p.135 + mpu->AddCycle(9); // Inside/Out p.135 uint32 offset = Decoder(addr); - return *(uint16 *)&mem[offset]; + return *(uint16 *)&mem[HLW(offset)]; } uint64 TVRAMDevice::Write8(uint32 addr, uint32 data) { uint32 offset = Decoder(addr); + uint8 *ptr; + uint8 mask8; - gMPU->AddCycle(9); // Inside/Out p.135 + mpu->AddCycle(9); // Inside/Out p.135 // XXX とりあえずね - putlog(3, "Write $%06x <- $%02x", addr, data); + putlog(3, "Write $%06x.B <- $%02x", addr, data); + + if (tvram.men) { + if ((addr & 1) == 0) { + mask8 = tvram.mask >> 8; + } else { + mask8 = tvram.mask; + } + } else { + mask8 = 0; + } + uint32 poffset = offset & 0x1ffff; if (tvram.sa) { // 同時アクセス - uint32 poffset = offset & 0x1ffff; for (int plane = 0; plane < 4; plane++) { if (tvram.ap[plane]) { - mem[HB(0x20000 * plane + poffset)] = data; + ptr = &mem[HLB(0x20000 * plane + poffset)]; + *ptr = (data & ~mask8) | (*ptr & mask8); } } } else { // 通常アクセス - mem[HB(offset)] = data; + ptr = &mem[HLB(offset)]; + *ptr = (data & ~mask8) | (*ptr & mask8); } - atomic_dirty[(offset % 0x20000) / 128] = 1; + SetDirty(poffset); return 0; } @@ -118,25 +127,35 @@ uint64 TVRAMDevice::Write16(uint32 addr, uint32 data) { uint32 offset = Decoder(addr); + uint16 *ptr; + uint16 mask16; - gMPU->AddCycle(9); // Inside/Out p.135 + mpu->AddCycle(9); // Inside/Out p.135 // XXX とりあえずね - putlog(3, "Write $%06x <- $%04x", addr, data); + putlog(3, "Write $%06x.W <- $%04x", addr, data); + + if (tvram.men) { + mask16 = tvram.mask; + } else { + mask16 = 0; + } + uint32 poffset = offset & 0x1ffff; if (tvram.sa) { // 同時アクセス - uint32 poffset = offset & 0x1ffff; for (int plane = 0; plane < 4; plane++) { if (tvram.ap[plane]) { - *(uint16 *)&mem[0x20000 * plane + poffset] = data; + ptr = (uint16 *)&mem[HLW(0x20000 * plane + poffset)]; + *ptr = (data & ~mask16) | (*ptr & mask16); } } } else { // 通常アクセス - *(uint16 *)&mem[offset] = data; + ptr = (uint16 *)&mem[HLW(offset)]; + *ptr = (data & ~mask16) | (*ptr & mask16); } - atomic_dirty[(offset % 0x20000) / 128] = 1; + SetDirty(poffset); return 0; } @@ -144,42 +163,83 @@ uint64 TVRAMDevice::Peek8(uint32 addr) { uint32 offset = Decoder(addr); - return mem[HB(offset)]; + return mem[HLB(offset)]; } -// 全画面の更新フラグを立てる -void -TVRAMDevice::Invalidate() +uint64 +TVRAMDevice::Poke8(uint32 addr, uint32 data) { - for (int i = 0; i < 1024; i++) { - atomic_dirty[i] = 1; + if ((int32)data >= 0) { + uint32 offset = Decoder(addr); + uint32 poffset = offset & 0x1ffff; + mem[HLB(offset)] = data; + // この場合も画面を更新する + SetDirty(poffset); } + return 0; +} + +// offset の位置に対応する更新フラグを立てる。 +// offset はプレーン先頭からのバイトオフセット。 +inline void +TVRAMDevice::SetDirty(uint32 offset) +{ + // VRAM は横 1024 ドット(128バイト) x 縦 1024 ドット。 + // + // 1 0 + // 3 2 1 0 f e d c b a 9 8 7 6 5 4 3 2 1 0 + // +-----------+-------+-----+-------+-----+ + // VRAM | Y座標 (1024dot) | X (128byte) : 8bit| + // +-----------+-------+-----+-------+-----+ + // : : + // +-------------------+ + // Dirty | | + // +-------------------+ + // 9 8 7 6 5 4 3 2 1 0 + + // 更新フラグはラスタ単位 + int y = offset >> 7; + dirty[y] = 1; +} + +// X 方向にはみ出したときに対応する仮想画面の Y 座標を返す。 +int +TVRAMDevice::GetWrapY(int y) const +{ + // はみ出すと次のラスタの先頭から表示するが、 + // TVRAM は 4 ラスタがひとかたまりで構成されていて、 + // 末尾 2bit が 3 のラスタの次は、末尾だけ 0 のラスタに戻る。ようだ。 + return (y & ~3) + ((y + 1) & 3); } void TVRAMDevice::SetScrollX(int x) { - tvram.xscroll = x; + xscroll = x; } void TVRAMDevice::SetScrollY(int y) { - tvram.yscroll = y; - // 全ラインを無効にする XXX とりあえずね - Invalidate(); + yscroll = y; + renderer->Invalidate2(); } -// CRTC R21 での同時アクセス設定を反映する -// CRTC から呼ばれる +// CRTC R21 での同時アクセス設定を反映する。 +// CRTC から呼ばれる。 void -TVRAMDevice::set_crtc_21(uint16 data) +TVRAMDevice::SetAccessPlane(uint16 data) { - tvram.sa = (data & 0x100); + tvram.cp[0] = (data & 0x01); + tvram.cp[1] = (data & 0x02); + tvram.cp[2] = (data & 0x04); + tvram.cp[3] = (data & 0x08); tvram.ap[0] = (data & 0x10); tvram.ap[1] = (data & 0x20); tvram.ap[2] = (data & 0x40); tvram.ap[3] = (data & 0x80); + tvram.sa = (data & 0x100); + tvram.men = (data & 0x200); if (0) { if (tvram.sa) { @@ -194,230 +254,82 @@ TVRAMDevice::set_crtc_21(uint16 data) } } -// テキスト画面合成 -bool -TVRAMDevice::Render(uint8 *imagebuf) +// CRTC R23 でのアクセスマスク設定を反映する。 +// CRTC から呼ばれる。 +void +TVRAMDevice::SetAccessMask(uint16 data) { - const uint32 *pal; - const uint16 *plane0; - const uint16 *plane1; - const uint16 *plane2; - const uint16 *plane3; - uint8 *dst0; - bool updated; - -#if defined(PERF) - perf.Start(); -#endif + tvram.mask = data; +} - dst0 = imagebuf; +// ラスターコピーを開始する。 +// src_ras, dst_ras はライン番号 (レジスタ設定値を4倍したもの)。 +void +TVRAMDevice::RasterCopy(int src_ras, int dst_ras) +{ + for (int plane = 0; plane < 4; plane++) { + if (tvram.cp[plane]) { + uint8 *src = &mem[0x20000 * plane + src_ras * 128]; + uint8 *dst = &mem[0x20000 * plane + dst_ras * 128]; + memcpy(dst, src, 128 * 4); + } + } - // 加工済みパレットを取得 - pal = gVideoCtlr->GetCookedPalette(); + // dst からの4ラインを全部 dirty にする。 + for (int y = 0; y < 4; y++) { + SetDirty((dst_ras + y) * 128); + } +} - updated = false; +// (x, y) 位置のピクセル情報を screen に描画。(ビットマップモニタの情報欄) +void +TVRAMDevice::UpdateInfo(TextScreen& screen, int x, int y) const +{ + screen.Clear(); - // dirty は VRAM への書き込みがあれば常に 1 にする。 - // レンダリング時に dirty が 1 なら 2 にしてレンダリング。 - // レンダリング終了時に dirty が 2 なら 0 に戻す。 - // これによりレンダリング中に VRAM への書き込みがあれば次回描画される。 - - // sy はメモリ上の Y 座標 - // dy はレンダラ画面の Y 座標 (CRTC のXXX を考慮) - // XXX スクロールは未実装 - - // テキスト画面 - plane0 = (uint16 *)&mem[0x00000]; - plane1 = (uint16 *)&mem[0x20000]; - plane2 = (uint16 *)&mem[0x40000]; - plane3 = (uint16 *)&mem[0x60000]; - int sy = tvram.yscroll; - for (int dy = 0; dy < 512; dst0 += 768 * Renderer::BPP, dy++, sy++) { - sy &= 0x3ff; - -#if defined(PERF) - // 計測中は常に全域を展開。 - // 起動直後のタイトルが消えるくらいまでではほぼ更新がなく - // ほぼ atomic_cas の時間を測定してるだけになってしまうので。 - atomic_dirty[sy] = 2; - if (1) { -#else - if (atomic_cas_8(&atomic_dirty[sy], 1, 2) == 1) { -#endif - uint32 *dst = (uint32 *)dst0; - uint32 *prb = rb; - - // sx はメモリ上の X オフセット (ワード単位) - // dx はレンダラ画面の X オフセット (ワード単位) - - int sx = sy * 1024 / 16; - for (int dx = 0; dx < 768 / 16; dx++, sx++) { -#if defined(__SSE2__) && !defined(NO_SSE) - __m128i a; - // 各プレーン 16 ビットを DWORD * 4 に読み込む。 - // plane0 = A, plane1 = B, plane2 = C, plane3 = D とすると - // a = $0000DDDD'0000CCCC'0000BBBB'0000AAAA; - a = _mm_set_epi32( - plane3[sx], - plane2[sx], - plane1[sx], - plane0[sx]); - // 各DWORD中の上位ワードに移動 - // a = $DDDD0000'CCCC0000'BBBB0000'AAAA0000; - a = _mm_slli_si128(a, 2); - for (int j = 0; j < 16; j++) { - // 各DWORDの最上位ビットを計4ビット取り出す - int c = _mm_movemask_ps(_mm_castsi128_ps(a)); - // 各DWORDを左シフト - a = _mm_add_epi32(a, a); - *prb++ = pal[c]; - } -#else - // t0..t3 は 16 ドット× 4 プレーン分の TVRAM 生データ。 - // このうち先頭の2ドットのカラーコードが %abcd, %efgh で - // 表される場合、t0..t3 は以下のようになっている。 - // t0 = %dhxxxxxx'xxxxxxxx - // t1 = %cgxxxxxx'xxxxxxxx - // t2 = %bfxxxxxx'xxxxxxxx - // t3 = %aexxxxxx'xxxxxxxx - // - // deptable[] によってこの t0..t3 の 16ビットを - // d0..d3 の 64ビット(16 nibble) に展開する。 - // d0 = %000x'000x' .. '000h'000d - // d1 = %000x'000x' .. '000g'000c - // d2 = %000x'000x' .. '000f'000b - // d3 = %000x'000x' .. '000e'000a - // - // これを 1ビットずつずらして合成する。 - // cc = %xxxx'xxxx' .. 'efgh'abcd - // - // 16進数で表すと - // cc = $PONMLKJI'HGFEDCBA で - // $A が +0 ドット目のカラーコード、 - // $B が +1 ドット目のカラーコード、 - // : - // $P が +15ドット目のカラーコード、 - // となる。 - - uint16 t0 = plane0[sx]; - uint16 t1 = plane1[sx]; - uint16 t2 = plane2[sx]; - uint16 t3 = plane3[sx]; - - union64 d0, d1, d2, d3; - d0.l = deptable[t0 >> 8]; - d1.l = deptable[t1 >> 8]; - d2.l = deptable[t2 >> 8]; - d3.l = deptable[t3 >> 8]; - d0.h = deptable[t0 & 0xff]; - d1.h = deptable[t1 & 0xff]; - d2.h = deptable[t2 & 0xff]; - d3.h = deptable[t3 & 0xff]; - - uint64 cc = d0.q | (d1.q << 1) | (d2.q << 2) | (d3.q << 3); - - for (int j = 0; j < 16; j++) { - uint32 c = cc & 0xf; - *prb++ = pal[c]; - cc >>= 4; - } -#endif - } + //0 1 2 3 4 5 6 + //012345678901234567890123456789012345678901234567890123456789012345 + //X=1024 Y=2024: (P0) $e01234: $00 (%00000000) ColorCode=$d -#if defined(__SSE4_1__) && !defined(NO_SSE) - // XBGR32 16ピクセル (16 ロングワード = 64バイト) から - // RGB24 16ピクセル (12 ロングワード = 48バイト) に変換する - for (int j = 0; j < 768; j += 16) { - // rb[0,1,2,3] = $00B0G0R0,$00B1G1R1,$00B2G2R2,$00B3G3R3 - // なので LE で 128ビット値としてレジスタに読み込むと - // a[0] = $00B3G3R3'00B2G2R2'00B1G1R1'00B0G0R0 となる。 - // - // ここは1文字が1バイトで x111 = $X1B1G1R1 だと思いねえ。 - // 入力の rb[0..15] と a[0..3] (どちらも64バイト) はこう。 - // rb[0,1,2,3] = a[0] = x333 x222 x111 x000 - // rb[4,5,6,7] = a[1] = x777 x666 x555 x444 - // rb[8,9,a,b] = a[2] = xbbb xaaa x999 x888 - // rb[c,d,e,f] = a[3] = xfff xeee xddd xccc - // - // suff(shuffle index) を使って a[0..3] を b[0..3] にする。 - // この次のステージで OR しやすい順を意識して並べ替える。 - // **** は使わないところ。 - // b[0] = **** 3332 2211 1000 - // b[1] = 5444 **** 7776 6655 - // b[2] = aa99 9888 **** bbba - // b[3] = fffe eedd dccc **** - // - // {P,Q,R,S} 表記を4ロングワードそれぞれに対するマスクとして、 - // c[0] = (b[0] & {0,1,1,1}) | (b[1] & {1,0,0,0}) - // c[1] = (b[1] & {0,0,1,1}) | (b[2] & {1,1,0,0}) - // c[2] = (b[2] & {0,0,0,1}) | (b[3] & {1,1,1,0}) - // で - // c[0] = 5444 3332 2211 1000 - // c[1] = aa99 9888 7776 6655 - // c[2] = fffe eedd dccc bbba - // ができる。 - - __m128i suff0 = _mm_set_epi8( - -1,-1,-1,-1, 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0); - __m128i suff1 = _mm_set_epi8( - 4, 2, 1, 0, -1,-1,-1,-1, 14,13,12,10, 9, 8, 6, 5); - __m128i suff2 = _mm_set_epi8( - 9, 8, 6, 5, 4, 2, 1, 0, -1,-1,-1,-1, 14,13,12,10); - __m128i suff3 = _mm_set_epi8( - 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0, -1,-1,-1,-1); - - __m128i a0 = _mm_load_si128((__m128i *)(&rb[j + 0])); - __m128i a1 = _mm_load_si128((__m128i *)(&rb[j + 4])); - __m128i a2 = _mm_load_si128((__m128i *)(&rb[j + 8])); - __m128i a3 = _mm_load_si128((__m128i *)(&rb[j + 12])); - - __m128i b0 = _mm_shuffle_epi8(a0, suff0); - __m128i b1 = _mm_shuffle_epi8(a1, suff1); - __m128i b2 = _mm_shuffle_epi8(a2, suff2); - __m128i b3 = _mm_shuffle_epi8(a3, suff3); - -#define PS(x) _mm_castsi128_ps(x) -#define SI(x) _mm_castps_si128(x) - __m128i c0 = SI(_mm_blend_ps(PS(b0), PS(b1), 0b1000)); - __m128i c1 = SI(_mm_blend_ps(PS(b1), PS(b2), 0b1100)); - __m128i c2 = SI(_mm_blend_ps(PS(b2), PS(b3), 0b1110)); - - _mm_storeu_si128((__m128i *)(&dst[0]), c0); - _mm_storeu_si128((__m128i *)(&dst[4]), c1); - _mm_storeu_si128((__m128i *)(&dst[8]), c2); - dst += 12; - } -#else - // xBGR32 4つ(16バイト)を RGB24 3つ(12バイト)に変換して - // ロングワードで書き出す。 - for (int j = 0; j < 768; j += 4) { - // rb[0] = $x0B0G0R0 - // rb[1] = $x1B1G1R1 - // rb[2] = $x2B2G2R2 - // rb[3] = $x3B3G3R3 - // - // dst[0] = $00B0G0R0 | $R1000000 = $R1B0G0R0 - // dst[1] = $0000B1G1 | $G2R20000 = $G2R2B1G1 - // dst[2] = $000000B2 | $B3G3R300 = $B3G3R3B2 - - *dst++ = le32toh((rb[j + 0] ) | (rb[j + 1] << 24)); - *dst++ = le32toh((rb[j + 1] >> 8) | (rb[j + 2] << 16)); - *dst++ = le32toh((rb[j + 2] >> 16) | (rb[j + 3] << 8)); - } -#endif + screen.Puts(0, 0, "X= Y="); + for (int i = 0; i < nplane; i++) { + screen.Print(15, i, "(P%d)", i); + } - // 2の時だけ0に戻す - atomic_cas_8(&atomic_dirty[sy], 2, 0); - updated = true; - } + // カーソルが範囲外ならここまで。 + if (x < 0) { + return; } -#if defined(PERF) - perf.End(); -#endif + screen.Print(2, 0, "%4d Y=%4d", x, y); - return updated; -} + // 1プレーン内のバイトオフセット + uint32 planeoffset = ((uint)y * 128) + ((uint)x / 8); + + uint cc = 0; + for (int i = 0; i < nplane; i++) { + // プレーン0先頭からのバイトオフセット + uint32 totaloffset = 0x20000 * i + planeoffset; + + uint8 data = mem[HLB(totaloffset)]; + screen.Print(15, i, "(P%d) $%06x: $%02x (%%", + i, baseaddr + totaloffset, data); + + // ビットパターン + for (int m = 0x80; m != 0; m >>= 1) { + screen.Putc((data & m) ? '1' : '0'); + } + screen.Putc(')'); -// スタティック変数 -/*static*/ uint32 TVRAMDevice::deptable[256]; + // カーソル位置をハイライト + screen.Locate(35 + (x % 8), i); + screen.SetAttr(TA::Em); + + // カラーコードを計算 + if ((data & (0x80U >> (x % 8))) != 0) { + cc |= (1U << i); + } + } + + screen.Print(45, 0, "ColorCode=$%x", cc); +}