|
|
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]);
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.