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

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

//
// プレーン VRAM (垂直 VRAM)
// Lunafb と X68k TVRAM の共通部分
//

#include "planevram.h"
#include "missing_bswap.h"
#include <algorithm>
#include <array>
#include <vector>

// コンストラクタ
PlaneVRAMDevice::PlaneVRAMDevice(int width_, int height_)
	: inherited(OBJ_PLANEVRAM)
{
	// ポカ避け。継承クラス側が設定する。
	nplane = -1;

	composite.Create(width_, height_);
}

// 初期化
bool
PlaneVRAMDevice::Init()
{
	// レンダリング時に使うテーブルを事前に計算。
	//
	// %abcdefgh の8ビットを
	// リトルエンディアンホストでは
	// %0000000h'0000000g'0000000f'0000000e'0000000d'0000000c'0000000b'0000000a
	// ビッグエンディアンホストでは
	// %0000000a'0000000b'0000000c'0000000d'0000000e'0000000f'0000000g'0000000h
	// の64ビットに伸張する。
	// (8ビットを各バイトの最下位ビットに対応させる)
	constexpr uint num = 256;
	deptable.reset(new(std::nothrow) uint64[num]);
	if ((bool)deptable == false) {
		warnx("Could not allocate %zu bytes at %s",
			sizeof(uint64) * num, __method__);
		return false;
	}
	for (uint i = 0; i < num; i++) {
		uint64 res = 0;
		for (int bit = 0; bit < 8; bit++) {
			int n = bit * 8;
#if BYTE_ORDER == LITTLE_ENDIAN
			n = 56 - n;
#endif
			res |= (uint64)((i >> bit) & 1) << n;
		}
		deptable[i] = res;
	}

	return true;
}

// デストラクタ
PlaneVRAMDevice::~PlaneVRAMDevice()
{
}

// リセット
void
PlaneVRAMDevice::ResetHard(bool poweron)
{
	// XXX ここ?
	Invalidate();
}

// 全画面の更新フラグを立てる
void
PlaneVRAMDevice::Invalidate()
{
	dirty.line.set();
}

// VRAM には更新がないが再レンダリングが必要。
// パレット変更、スクロール等。VM スレッドから呼ばれる。
void
PlaneVRAMDevice::Invalidate2()
{
	dirty.invalidate2 = true;
}

// 画面更新する必要があるか。
// dirty から pending を更新してみて、更新が一つでもあれば true を返す。
// VM スレッドで呼ばれる。
bool
PlaneVRAMDevice::Snap()
{
	bool update_needed;

	// 更新 (dirty) があれば pending に重ねる。
	{
		std::lock_guard<std::mutex> lock(mtx);
		pending.line |= dirty.line;
		dirty.line.reset();

		if (__predict_false(dirty.invalidate2)) {
			pending.invalidate2 = true;
			dirty.invalidate2 = false;
		}

		update_needed = pending.line.any() || pending.invalidate2;
	}

	return update_needed;
}

// 画面合成。
// レンダラスレッドから呼ばれる。
// dst を更新すれば true を返す。
bool
PlaneVRAMDevice::Render(BitmapRGBX& dst)
{
	bool updated = false;

	// ローカルにコピー。
	ModifyInfo modified;
	{
		std::lock_guard<std::mutex> lock(mtx);
		modified.line = pending.line;
		modified.invalidate2 = pending.invalidate2;
		pending.line.reset();
		pending.invalidate2 = false;
	}

	// VRAM に更新があれば composite を更新。
	if (modified.line.any()) {
		RenderVRAMToComposite(modified.line);
		updated = true;
	}

	// composite に更新があるか(updated)、
	// 無条件に更新するか(modified.invalidate2) なら dst を更新。
	if (updated || modified.invalidate2) {
		RenderCompositeToRGBX(dst, modified);
		updated = true;
	}

	return updated;
}

// VRAM から composite 画面を合成する。
// レンダラスレッドから呼ばれる。
void
PlaneVRAMDevice::RenderVRAMToComposite(const bitset1024& modified)
{
	const uint width  = composite.GetWidth();
	const uint height = composite.GetHeight();
	const uint planesz = (width / 8) * height;

	// 1bpp はパレット的にはほぼ 4bpp と同じ扱いになる。
	const int nplane4 = (nplane == 1) ? 4 : nplane;

	// テキスト合成画面を描く
	for (uint y = 0; y < height; y++) {
		if (modified.test(y) == false) {
			continue;
		}

		const uint32 *src = (const uint32 *)&mem[0];
		src += y * width / 32;
		uint64 *dst = (uint64 *)composite.GetRowPtr(y);

		for (uint x = 0; x < width / 32; x++) {
			std::array<uint32, 8> data;

			// VRAM はホストエンディアンに関係なく 32bit で読み込んだ
			// 時の MSB が左端ピクセル。
			// ここでは左側から 8ピクセル(8ビット)ずつ 4回処理する
			// のを最下位バイトから順に右シフトで行いたいので、
			// (バイト内のビット順は維持したまま) バイトスワップする。
			//
			//          31     24  23     16  15      8   7      0
			//        +----------+----------+----------+----------+
			// VRAM   |+00 .. +07|+08 .. +15|+16 .. +23|+24 .. +31|
			//        +----------+----------+----------+----------+
			//  ↓
			//        +----------+----------+----------+----------+
			// data[] |+24 .. +31|+16 .. +23|+08 .. +15|+00 .. +07|
			//        +----------+----------+----------+----------+
			if (__predict_false(nplane == 1)) {
				// LUNA 1bpp の場合残りの3プレーンは ff で埋める。
				// 実際にも存在しないプレーンは ff が読み出せて
				// カラーインデックスは $e か $f の二択になるので。
				data[0] = bswap32(src[0]);
				data[1] = 0xffffffff;
				data[2] = 0xffffffff;
				data[3] = 0xffffffff;
			} else {
				uint offset = planesz / sizeof(uint32);
				for (int plane = 0; plane < nplane; plane++) {
					data[plane] = bswap32(src[plane * offset]);
				}
			}
			src++;

			// data[] の下位 8bit ずつを取り出して deptable[] で変換。
			// p0..p3 は1バイト x 8個。
			//
			// data[] = %xxxxxxxx'xxxxxxxx'xxxxxxxx'ABCDEFGH
			// ↓
			// (リトルエンディアンホストの場合)
			// p.H = %0000000H'0000000G'0000000F'0000000E
			// p.L = %0000000D'0000000C'0000000B'0000000A
			for (int b = 0; b < 4; b++) {
				uint64 cc = 0;

				for (int plane = 0; plane < nplane4; plane++) {
					uint64 p;
					p = deptable[data[plane] & 0xff];
					p <<= plane;
					cc |= p;
					data[plane] >>= 8;
				}

				// cc (64bit = 8バイト) は各バイトが各ピクセルの
				// カラーインデックスになっているので
				// BitmapI8 に 8バイト一気に書き込める。
				// (エンディアンの影響は deptable[] で処理してある)
				*dst++ = cc;
			}
		}
	}
}

// composite 画面とパレット情報から RGBX 画面を合成する。
// modified は composite 上での変更箇所を示している。
// レンダラスレッドから呼ばれる。
void
PlaneVRAMDevice::RenderCompositeToRGBX(BitmapRGBX& dst,
	const ModifyInfo& modified)
{
	const uint width  = dst.GetWidth();
	const uint height = dst.GetHeight();

	// view_mod は表示領域 view における更新ラスタ
	BitmapI8 view(width, height);
	std::vector<uint8> view_mod(height);

	// この view に対する modify を計算する
	if (__predict_false(modified.invalidate2)) {
		std::fill(view_mod.begin(), view_mod.end(), 1);
	} else {
		// Y scroll 方向の modify 計算
		int sy = yscroll;
		for (int y = 0; y < height; y++, sy++) {
			if (sy >= composite.GetHeight()) {
				sy -= composite.GetHeight();
			}
			view_mod[y] = modified.line[sy];
		}

		// 右がはみ出す場合は、はみ出した先のラスタも加味する。
		if (xscroll + view.GetWidth() > composite.GetWidth()) {
			sy = yscroll;
			for (int y = 0; y < height; y++, sy++) {
				if (sy >= composite.GetHeight()) {
					sy -= composite.GetHeight();
				}
				int sy2 = GetWrapY(sy);
				view_mod[y] |= modified.line[sy2];
			}
		}
	}

	// composite からスクロールを加味した表示領域 view を作る
	int sy = yscroll;
	for (int y = 0; y < height; y++, sy++) {
		if (sy >= composite.GetHeight()) {
			sy -= composite.GetHeight();
		}
		if (view_mod[y] == 0) {
			continue;
		}

		uint8 *v = view.GetRowPtr(y);
		uint8 *c = composite.GetPtr(xscroll, sy);
		if (xscroll + view.GetWidth() <= composite.GetWidth()) {
			memcpy(v, c, view.GetWidth());
		} else {
			// はみ出しの折り返し
			int len1 = composite.GetWidth() - xscroll;
			memcpy(v, c, len1);
			v += len1;
			int sy2 = GetWrapY(sy);
			c = composite.GetRowPtr(sy2);
			int len2 = view.GetWidth() - len1;
			memcpy(v, c, len2);
		}
	}

	// view (I8) を Bitmap (RGBX) に展開する
	dst.DrawBitmapI8Raster(view, palette, &view_mod[0]);
}

unix.superglobalmegacorp.com

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