File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / bitmap.cpp
Revision 1.1.1.6 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:04:52 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v010, HEAD
nono 0.1.6

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

#include "bitmap.h"
#include "bt454.h"
#include "cgrom.h"
#include "mpu.h"

//
// ビットマッププレーン
//
// Bitmap の VRAM はロングワード単位でホストバイトオーダ配置なので、
// バイトアクセス、ワードアクセス時は HLB(), HLW() マクロを使用のこと。
//

// SX-9100 のほうはこう書いてあってこっちが正しいようだ。
//      +00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
// b100 |RF|        |BS|        |共 通 B M P|プレーン#0 |
// b110 |プレーン#1 |プレーン#2 |プレーン#3 |プレーン#4 |
// b120 |プレーン#5 |プレーン#6 |プレーン#7 |FC|        |
// b130 |F0|        |F1|        |F2|        |F3|        |
// b140 |F4|        |F5|        |F6|        |F7|        |
//
// SX-7100 のほうはこう書いてあるがこうはなっていないようだ。
//      +00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
// b100 |RF|        |BS|        |共 通 B M P|FC|        |
// b110 |プレーン#0 |プレーン#1 |プレーン#2 |プレーン#3 |
// b120 |プレーン#4 |プレーン#5 |プレーン#6 |プレーン#7 |
// b130 |F0|        |F1|        |F2|        |F3|        |
// b140 |F4|        |F5|        |F6|        |F7|        |
//
// RF: RFCNT (4バイト)
// BS: BMSEL (4バイト)
// FC: 共通ファンクションセット
// Fn: プレーン#n ファンクションセット
// 1区画が 64KB。空白のところは未調査。
// 未装着のプレーン(#4..#7とか)は 0xff が読めるようだ。
// ファンクションセットが 64バイトで折り返してるか未調査だが、
// b130'0000 も b133'0000 も 0xff が読めるのでこの範囲全域でミラーかも。
// b150'0000 以降(b1ff'ffffまで) は BUSERR のようだ。
//

std::unique_ptr<BitmapDevice> gBitmap;

BitmapDevice::BitmapDevice()
{
	logname = "bitmap";
	devname = "Bitmap";
	devaddr = 0xb1000000;
	monitor_size = nnSize(42, 11);
}

BitmapDevice::~BitmapDevice()
{
}

bool
BitmapDevice::Init()
{
	// XXX とりあえず。実際は設定による
	nplane = 1;
	// プレーン数によってメモリを確保
	mem.reset(new uint8 [0x40000 * nplane]);

	return true;
}

bool
BitmapDevice::PowerOn()
{
	// XXX 実際は不定値だと思うけど、観測手段がないのでとりあえず。
	memset(&mem[0], 0xff, 0x40000 * nplane);
	return true;
}

void
BitmapDevice::ResetHard()
{
	// 厳密にどの時点でリセットされるのかは知らないけど
	bmsel = 0;

	// XXX 初期値の観測は困難なので適当
	rfcnt = 0;
	hscroll = 0;
	vscroll = 0;

	for (int i = 0; i < MAX_PLANES; i++) {
		fcs[i] = 5;
		mask[i] = 0xffffffff;
	}

	// XXX ここ?
	Invalidate();
}

uint64
BitmapDevice::Read8(uint32 addr)
{
	// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
	gMPU->AddCycle(1);

	if (addr < plane0base) {
		// 書き込み専用
		return 0xff;
	}
	if (addr < plane0base + 0x40000 * nplane) {
		// プレーンの読み出し
		return mem[HLB(addr - plane0base)];
	}
	// 残りは書き込み専用
	return 0xff;
}

uint64
BitmapDevice::Read16(uint32 addr)
{
	// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
	gMPU->AddCycle(1);

	if (addr < plane0base) {
		// 書き込み専用
		return 0xffff;
	}
	if (addr < plane0base + 0x40000 * nplane) {
		// プレーンの読み出し
		return *(uint16 *)&mem[HLW(addr - plane0base)];
	}
	// 残りは書き込み専用
	return 0xffff;
}

uint64
BitmapDevice::Read32(uint32 addr)
{
	// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
	gMPU->AddCycle(1);

	if (addr < plane0base) {
		// 書き込み専用
		return 0xffffffff;
	}
	if (addr < plane0base + 0x40000 * nplane) {
		// プレーンの読み出し
		return *(uint32 *)&mem[addr - plane0base];
	}
	// 残りは書き込み専用
	return 0xffffffff;
}

uint64
BitmapDevice::Write8(uint32 addr, uint32 data)
{
	// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
	gMPU->AddCycle(1);

	switch (addr) {
	 case 0xb1000000:
	 case 0xb1000001:
	 case 0xb1000002:
	 case 0xb1000003:
		// このロングワード内へのバイトアクセスはロングワードにミラーされる
		data = (data << 24) | (data << 16) | (data << 8) | data;
		return Write32(addr & ~3, data);
	 case 0xb1040000:
	 case 0xb1040001:
	 case 0xb1040002:
	 case 0xb1040003:
		// BMSEL はバイトアクセスできる?
		putlog(0, "未実装バイト書き込み $%08x <- $%02x", addr, data);
		return 0;
	}

	if (addr < 0xb1080000) {
		putlog(0, "未実装バイト書き込み $%08x <- $%02x", addr, data);
		return 0;
	}
	if (addr < plane0base) {
		// 共通ビットマッププレーン
		WriteCommonBitmap8(addr, data);
		return 0;
	}

	if (addr < plane7end) {
		// ビットマッププレーン
		uint32 reladdr = addr - plane0base;
		uint plane = reladdr / 0x40000;
		uint32 offset = reladdr % 0x40000;

		if (plane < nplane) {
			// ビットマッププレーンN
			putlog(2, "プレーン%d書き込み $%08x <- $%02x", plane, addr, data);
			WritePlane8(plane, offset, data);
			return 0;
		}
		// HW 設定によっては存在しないプレーンへの書き込み
		putlog(3, "不在プレーン%d書き込み $%08x <- $%02x",
			plane, addr, data);
		return 0;
	}

	putlog(0, "未実装バイト書き込み $%08x <- $%02x", addr, data);
	return 0;
}

uint64
BitmapDevice::Write16(uint32 addr, uint32 data)
{
	// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
	gMPU->AddCycle(1);

	switch (addr) {
	 case 0xb1000000:
	 case 0xb1000002:
		// このロングワード内へのワードアクセスはロングワードにミラーされる
		data = (data << 16) | data;
		return Write32(addr & ~3, data);
	 case 0xb1040000:
	 case 0xb1040002:
		// BMSEL はワードアクセスできる?
		putlog(0, "未実装ワード書き込み $%08x <- $%04x", addr, data);
		return 0;
	}

	if (addr < 0xb1080000) {
		putlog(0, "未実装ワード書き込み $%08x <- $%04x", addr, data);
		return 0;
	}
	if (addr < plane0base) {
		// 共通ビットマッププレーン
		WriteCommonBitmap16(addr, data);
		return 0;
	}

	if (addr < plane7end) {
		// ビットマッププレーン
		uint32 reladdr = addr - plane0base;
		uint plane = reladdr / 0x40000;
		uint32 offset = reladdr % 0x40000;

		if (plane < nplane) {
			// ビットマッププレーンN
			putlog(2, "プレーン%d書き込み $%08x <- $%04x", plane, addr, data);
			WritePlane16(plane, offset, data);
			return 0;
		}
		// HW 設定によっては存在しないプレーンへの書き込み
		putlog(3, "不在プレーン%d書き込み $%08x <- $%04x",
			plane, addr, data);
		return 0;
	}

	putlog(0, "未実装ワード書き込み $%08x <- $%04x", addr, data);
	return 0;
}

uint64
BitmapDevice::Write32(uint32 addr, uint32 data)
{
	// XXX とりあえず RAM と同じウェイトを入れておく (未調査)
	gMPU->AddCycle(1);

	switch (addr) {
	 case 0xb1000000:
		// RFCNT
		WriteRFCNT(data);
		return 0;

	 case 0xb1040000:
		// BMSEL
		bmsel = data & 0xf;
		putlog(1, "BMSEL <- $%08x", data);
		return 0;
	}

	if (addr < 0xb1080000) {
		putlog(0, "未実装ロング書き込み $%08x <- $%08x", addr, data);
		return 0;
	}
	if (addr < plane0base) {
		// 共通ビットマッププレーン
		WriteCommonBitmap32(addr, data);
		return 0;
	}

	if (addr < plane7end) {
		// ビットマッププレーン
		uint32 reladdr = addr - plane0base;
		uint plane = reladdr / 0x40000;
		uint32 offset = reladdr % 0x40000;

		if (plane < nplane) {
			// ビットマッププレーンN
			putlog(3, "プレーン%d書き込み $%08x <- $%08x", plane, addr, data);
			WritePlane32(plane, offset, data);
			return 0;
		}
		// HW 設定によっては存在しないプレーンへの書き込み
		putlog(3, "不在プレーン%d書き込み $%08x <- $%08x",
			plane, addr, data);
		return 0;
	}

	if (addr < 0xb1300000) {
		// 共通ファンクションセット
		uint rop = (addr & 0x3f) / 4;
		putlog(2, "共通FCSet 書き込み $%08x <- $%08x (%s)",
			addr, data, RopStr(rop));
		for (int i = 0; i < nplane; i++) {
			if ((bmsel & (1 << i))) {
				fcs[i] = rop;
				mask[i] = data;
			}
		}
		return 0;
	}

	if (addr < fcset7end) {
		// ファンクションセット
		int plane = (addr - 0xb1300000) / 0x40000;

		if (plane < nplane) {
			// ファンクションセットN
			uint rop = (addr & 0x3f) / 4;
			putlog(2, "FCSet%d書き込み $%08x <- $%08x (%s)",
				plane, addr, data, RopStr(rop));
			fcs[plane] = rop;
			mask[plane] = data;
			return 0;
		}
		// HW 設定によっては存在しない FCSet への書き込み
		putlog(3, "不在FCSet%d書き込み $%08x <- $%08x",
			plane, addr, data);
		return 0;
	}

	putlog(0, "未実装ロング書き込み $%08x <- $%08x", addr, data);
	return 0;
}

uint64
BitmapDevice::Peek8(uint32 addr)
{
	if (addr < plane0base) {
		// 書き込み専用
		return 0xff;
	}
	if (addr < plane0base + 0x40000 * nplane) {
		// プレーンの読み出し
		return mem[HLB(addr - plane0base)];
	}
	// 残りは書き込み専用
	return 0xff;
}

void
BitmapDevice::MonitorUpdate(TextScreen& monitor)
{
	int i;

	monitor.Clear();

	// 0         1         2         3         4
	// 012345678901234567890123456789012345678901
	// RFCNT: $12345678 (X=-123dot, Y=1023dot)
	//
	//    BMSEL=$4 ROP  Mask           RGB    RGB
	// Plane#0 Sel THRU $ffffffff   $0:RGB $8:RGB
	monitor.Print(0, 0, "RFCNT: $%08x (X=%ddot, Y=%ddot)",
		rfcnt, hscroll, vscroll);
	monitor.Print(3, 2, "BMSEL=$%x ROP  Mask", bmsel);

	for (i = 0; i < nplane; i++) {
		monitor.Print(0, 3 + i, "Plane#%d %s %s $%08x",
			i,
			(bmsel & (1 << i)) ? "Sel" : "---",
			RopStr(fcs[i]), mask[i]);
	}
	for (; i < MAX_PLANES; i++) {
		monitor.Print(0, 3 + i, TA::Disable, "Plane#%d (Not installed)", i);
	}

	// ついでなのでここで BT454 のパレットも表示。
	// 012345
	// $0:RGB
	const uint8 *pal = gBT454->reg.color;
	monitor.Puts(29, 1, "<BT454>");
	monitor.Puts(32, 2, "RGB    RGB");
	for (i = 0; i < 16; i++) {
		uint rgb =
			((pal[i * 3 + 0] & 0xf0) << 4) +
			((pal[i * 3 + 1] & 0xf0)     ) +
			((pal[i * 3 + 2] & 0xf0) >> 4);
		monitor.Print(29 + (i / 8) * 7, (i % 8) + 3, "$%x:%03x", i, rgb);
	}
}

// 全画面の更新フラグを立てる
void
BitmapDevice::Invalidate()
{
	for (int i = 0; i < 1024; i++) {
		atomic_dirty[i] = 1;
	}
}

// RFCNT への書き込み
void
BitmapDevice::WriteRFCNT(uint32 data)
{
	// レジスタ値
	rfcnt = data;

	// 水平オフセットを計算
	// XXX マニュアルでは -8 まで指定できるように読めるが
	//     指定する方法が分からない。
	// XXX 193 以上を指定するとどうなるのか (とりあえずほっとく)
	//     実機の挙動を目分量で見ると、右にはみ出た部分は
	//     256 ラスタ先のラスタデータを描画しているようだ。
	hscroll = ((rfcnt >> 16) & 0xff) + 8;
	hscroll *= 4;

	// ラスタ(垂直)カウンタ
	vscroll = (rfcnt + 26) & 0x3ff;

	// 全画面更新
	Invalidate();

	putlog(1, "RFCNT <- $%08x (水平 %ddot, 垂直 %ddot)",
		data, hscroll, vscroll);
}

// 共通ビットマッププレーン バイト書き込み
void
BitmapDevice::WriteCommonBitmap8(uint32 addr, uint32 data)
{
	uint32 offset = addr - 0xb1080000;
	putlog(2, "共通プレーン書き込み $%08x <- $%02x", addr, data);
	for (int i = 0; i < nplane; i++) {
		if ((bmsel & (1 << i))) {
			WritePlane8(i, offset, data);
		}
	}
}

// 共通ビットマッププレーン ワード書き込み
void
BitmapDevice::WriteCommonBitmap16(uint32 addr, uint32 data)
{
	uint32 offset = addr - 0xb1080000;
	putlog(2, "共通プレーン書き込み $%08x <- $%04x", addr, data);
	for (int i = 0; i < nplane; i++) {
		if ((bmsel & (1 << i))) {
			WritePlane16(i, offset, data);
		}
	}
}

// 共通ビットマッププレーン ロング書き込み
void
BitmapDevice::WriteCommonBitmap32(uint32 addr, uint32 data)
{
	uint32 offset = addr - 0xb1080000;
	putlog(2, "共通プレーン書き込み $%08x <- $%08x", addr, data);
	for (int i = 0; i < nplane; i++) {
		if ((bmsel & (1 << i))) {
			WritePlane32(i, offset, data);
		}
	}
}

static inline uint32
RasterOp(uint rop, uint32 data, uint32 m)
{
	switch (rop) {
	 case 0:	// ZERO
		data = 0;
		break;
	 case 1:	// AND1
		data &= m;
		break;
	 case 2:	// AND2
		data = ~data & m;
		break;
	 case 3:	// 使用不可
		// 本当はここにシリアルモード変更コマンドがあるらしい
		data = m;
		break;
	 case 4:	// AND3
		data &= ~m;
		break;
	 case 5:	// THROUGH
		break;
	 case 6:	// EOR
		data ^= m;
		break;
	 case 7:	// OR1
		data |= m;
		break;
	 case 8:	// NOR
		data = ~data & ~m;
		break;
	 case 9:	// ENOR
		data = (data & m) | (~data & ~m);
		break;
	 case 10:	// INV1
		data = ~data;
		break;
	 case 11:	// OR2
		data = ~data | m;
		break;
	 case 12:	// INV2
		data = ~m;
		break;
	 case 13:	// OR3
		data |= ~m;
		break;
	 case 14:	// NAND
		data = ~data | ~m;
		break;
	 case 15:	// ONE
		data = 0xffffffff;
		break;
	}
	return data;
}

// ROP 文字列を返す
const char *
BitmapDevice::RopStr(uint rop)
{
	static const char * ropstr[] = {
		"ZERO",
		"AND1",
		"AND2",
		"(3)",
		"AND3",
		"THRU",	// THROUGH
		"EOR",
		"OR1",
		"NOR",
		"ENOR",
		"INV1",
		"OR2",
		"INV2",
		"OR3",
		"NAND",
		"ONE",
	};
	return ropstr[rop];
}

// プレーン バイト書き込み
void
BitmapDevice::WritePlane8(uint plane, uint32 offset, uint32 data)
{
	uint32 memoffset = plane * 0x40000 + offset;
	uint32 m;
	uint32 mask8;

	m = mem[HLB(memoffset)];
	data = RasterOp(fcs[plane], data, m);
	mask8 = mask[plane] >> (24 - (memoffset & 3) * 8);
	mem[HLB(memoffset)] = (data & mask8) | (m & ~mask8);
	atomic_dirty[offset / 256] = 1;
}

// プレーン ワード書き込み
void
BitmapDevice::WritePlane16(uint plane, uint32 offset, uint32 data)
{
	uint32 memoffset = plane * 0x40000 + offset;
	uint32 m;
	uint32 mask16;

	m = *(uint16 *)&mem[HLW(memoffset)];
	data = RasterOp(fcs[plane], data, m);
	if ((memoffset & 2) == 0) {
		// 上位ワード
		mask16 = mask[plane] >> 16;
	} else {
		// 下位ワード
		mask16 = mask[plane];
	}
	*(uint16 *)&mem[HLW(memoffset)] = (data & mask16) | (m & ~mask16);
	atomic_dirty[offset / 256] = 1;
}

// プレーン ロングワード書き込み
void
BitmapDevice::WritePlane32(uint plane, uint32 offset, uint32 data)
{
	uint32 memoffset = plane * 0x40000 + offset;
	uint32 m;

	m = *(uint32 *)&mem[memoffset];
	data = RasterOp(fcs[plane], data, m);
	*(uint32 *)&mem[memoffset] = (data & mask[plane]) | (m & ~mask[plane]);
	atomic_dirty[offset / 256] = 1;
}


// エミュレータによる画面出力
//
// VM 内からの操作ではなく、エミュレータ本体から画面を操作する
// エミュレーション ROM によるモニタコンソール用の機能。
// サービスを使いたい側 (EmuROM など) が任意の大きさの TextScreen を
// 用意し EmuConsoleOpen() をコールすると (現在のパレットのまま) 画面を
// クリアし、画面中央をこのテキスト VRAM の表示エリアとみなす。
// 以降は EmuConsoleUpdate() で TextScreen の内容に従って画面を更新し、
// 使用後は EmuConsoleClose() で再び画面をクリア (およびリソースの解放) を
// すること。

// エミュレータによる出力開始
void
BitmapDevice::EmuConsoleOpen(TextScreen *t)
{
	textscr = t;

	screen_w = textscr->GetCol();
	screen_h = textscr->GetRow();
	prev.resize(screen_w * screen_h);
	for (int i = 0; i < screen_w * screen_h; i++) {
		prev[i] = 0x0020;
	}

	// ざっくりセンタリングする
	origin_x = (1280 - screen_w * 12) / 2;
	origin_y = (1024 - screen_h * 24) / 2;
	// ただし横は 48ドット境界から始める
	// こうすると1(,2)文字目の24ビットが必ずワード境界から始まる。
	origin_x = (origin_x / 48) * 48;

	// 同時書き込み設定
	bmsel = (1 << nplane) - 1;

	// 全画面をクリア
	for (uint32 addr = 0xb1080000; addr < 0xb10c0000; addr += 4) {
		WriteCommonBitmap32(addr, 0);
	}
}

// エミュレータによる出力終了
void
BitmapDevice::EmuConsoleClose()
{
	// 全画面をクリア
	for (uint32 addr = 0xb1080000; addr < 0xb10c0000; addr += 4) {
		WriteCommonBitmap32(addr, 0);
	}
	// 同時書き込みも元に戻す
	bmsel = 0;

	textscr = NULL;
	prev.clear();
}

// エミュレータによる出力
// ts が出力するテキスト情報、oldts は前回の情報。
void
BitmapDevice::EmuConsoleUpdate()
{
	auto& text = textscr->GetBuf();

	// 12ドットフォントなので2文字ずつ (24ビットずつ) 更新する
	for (int y = 0; y < screen_h; y++) {
		for (int x = 0; x < screen_w; x += 2) {
			// 2文字分の文字と属性
			uint32 t32 = *(const uint32 *)&text[y * screen_w + x];
			uint32 p32 = *(const uint32 *)&prev[y * screen_w + x];
			if (t32 == p32) {
				continue;
			}
			// ここからは変更があったので出力
			*(uint32 *)&prev[y * screen_w + x] = t32;
			// 2文字分として直接32ビットキャストして取り出した t32 だと
			// エンディアンによって上下が変わるので、ここでは改めて
			// 順序通りに2回取り出す
			uint32 ch0 = text[y * screen_w + x];
			uint32 ch1 = text[y * screen_w + x + 1];
			// その文字のフォントデータ上でのオフセット
			const uint8 * const fo0 = BuiltinCGROM::Get12x24(ch0 & 0xff);
			const uint8 * const fo1 = BuiltinCGROM::Get12x24(ch1 & 0xff);

			// 1,2文字目なら true、3,4文字目なら false
			bool evenpair = ((x & 2) == 0);

			// ビットマップ上の書き始めアドレス
			uint32 addr = 0xb1080000 +
				((origin_y + y * 24) * 256) +
				((origin_x + x * 12) / 8);

			for (int i = 0; i < 24; i++) {
				// その文字のフォントデータ
				uint32 fdat0 = be16toh(*(const uint16*)(fo0 + i * 2));
				uint32 fdat1 = be16toh(*(const uint16*)(fo1 + i * 2));

				// 属性はとりあえず反転のみサポート
				if ((ch0 & TA::On)) {
					fdat0 = ~fdat0 & 0xfff0;
				}
				if ((ch1 & TA::On)) {
					fdat1 = ~fdat1 & 0xfff0;
				}

				// 1文字目と2文字目を組み合わせて 24bit にする
				uint32 data = (fdat0 << 8) | (fdat1 >> 4);

				// 書き込みは一度に 3バイトずつになるので、
				// 効率化のため上位下位ペアで書き込みパターンを変える。
				//
				// | chr1 | chr2  | chr3 | chr4  |
				// +----+----+----+----+----+----+
				// | +0 | +1 | +2 | +3 | +4 | +5 |
				// +----+----+----+----+----+----+
				// |<-.Word->|<.B>|<.B>|<-.Word->|
				if (evenpair) {
					WriteCommonBitmap16(addr, data >> 8);
					WriteCommonBitmap8(addr + 2, data & 0xff);
				} else {
					WriteCommonBitmap8(addr, data >> 16);
					WriteCommonBitmap16(addr + 1, data & 0xffff);
				}

				addr += 256;	// 1ライン進める
			}
		}
	}
}

unix.superglobalmegacorp.com

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