--- nono/vm/planevram.cpp 2026/04/29 17:05:10 1.1.1.1 +++ nono/vm/planevram.cpp 2026/04/29 17:05:17 1.1.1.2 @@ -15,20 +15,16 @@ #include #endif -// グローバル参照用 -PlaneVRAMDevice *gPlaneVRAM; - // コンストラクタ -PlaneVRAMDevice::PlaneVRAMDevice(const std::string& objname_, - int width_, int height_) - : inherited(objname_) +PlaneVRAMDevice::PlaneVRAMDevice(int width_, int height_) + : inherited(OBJ_PLANEVRAM) { - // ポカ避け。lunafb.cpp の Init() あたりのコメント参照。 + // ポカ避け。継承クラス側が設定する。 nplane = -1; composite.Create(width_, height_); - dirty.resize((width_ / BLKX) * (height_ / BLKY)); + dirty.resize(height_); // レンダリング時に使うテーブルを事前に計算。 InitDeptable(); @@ -37,7 +33,6 @@ PlaneVRAMDevice::PlaneVRAMDevice(const s // デストラクタ PlaneVRAMDevice::~PlaneVRAMDevice() { - gPlaneVRAM = NULL; } // リセット @@ -87,9 +82,6 @@ void PlaneVRAMDevice::ClearDirty() { std::fill(dirty.begin(), dirty.end(), 0); -#if defined(PERF_HIT) - std::fill(dirty_word.begin(), dirty_word.end(), false); -#endif } // 更新情報を取得。 @@ -97,36 +89,15 @@ PlaneVRAMDevice::ClearDirty() ModifyInfo PlaneVRAMDevice::GetModify() const { - // dirty は高速化のためバイトあたり 1bit しか使っていないが、 - // ModifyInfo はスレッド間の交換形式なのでビットを詰め込んだものにする。 - // mod.bits[] 1ワード(16bit)を横一列分として 64行分。 - // TVRAM の場合は横 1024 ドットしかないので、左から 8bit のみ有効、 - // 残りのビットは %0 にすること。 - - const int width = composite.GetWidth(); - const int height = composite.GetHeight(); ModifyInfo mod; - - static_assert(mod.bits.size() == 1024 / BLKY, ""); - static_assert(sizeof(mod.bits[0]) * 8 == 2048 / BLKX, ""); - - int i = 0; - for (int y = 0; y < height / BLKY; y++) { - uint16 m = 0x8000; - uint16 t = 0; - for (int x = 0; x < width / BLKX; x++, i++, m >>= 1) { - if (dirty[i]) { - t |= m; - mod.n_dirty++; - } + memcpy(&mod.bits[0], &dirty[0], mod.bits.size()); + // TODO: 互換性のために n_dirty 計算。廃止するかも。 + for (int i = 0; i < dirty.size(); i++) { + if (dirty[i]) { + mod.n_dirty++; } - mod.bits[y] = t; } -#if defined(PERF_HIT) - mod.dirty_word = dirty_word; -#endif - return mod; } @@ -162,83 +133,77 @@ PlaneVRAMDevice::RenderVRAMToComposite(c const int height = composite.GetHeight(); const int planesz = (width / 8) * height; + // 1bpp はパレット的にはほぼ 4bpp と同じ扱いになる。 + const int nplane4 = (nplane == 1) ? 4 : nplane; + // テキスト合成画面を描く - for (int by = 0; by < height / BLKY; by++) { - // 横1列分 - uint16 mod = modify.bits[by]; - - for (int bx = 0; mod != 0; bx++, mod <<= 1) { - if ((int16)mod >= 0) { - continue; + for (int y = 0; y < height; y++) { + uint8 mod = modify.bits[y]; + if (mod == 0) { + continue; + } + + const uint32 *src = (const uint32 *)&mem[0]; + src += y * width / 32; + uint64 *dst = (uint64 *)composite.GetPtr(0, y); + + for (int x = 0; x < width / 32; x++) { + std::array 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 { + int offset = planesz / sizeof(uint32); + for (int plane = 0; plane < nplane; plane++) { + data[plane] = bswap32(src[plane * offset]); + } } + src++; - // 変更のあった1ブロックを更新 - for (int y = by * BLKY, yend = y + BLKY; y < yend; y++) { - int x = bx * BLKX; // ピクセル単位 - - const uint32 *src = (const uint32 *)&mem[0]; - src += y * width / 32 + (x / 32); - uint64 *dst = (uint64 *)composite.GetPtr(x, y); - - for (int j = 0; j < BLKX / 32; j++) { - std::array 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 { - int 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 - // ↓ - // (リトルエンディアンホストの場合) - // pX.H = %0000000H'0000000G'0000000F'0000000E - // pX.L = %0000000D'0000000C'0000000B'0000000A - for (int b = 0; b < 4; b++) { - uint64 p0 = deptable[data[0] & 0xff]; - uint64 p1 = deptable[data[1] & 0xff]; - uint64 p2 = deptable[data[2] & 0xff]; - uint64 p3 = deptable[data[3] & 0xff]; - data[0] >>= 8; - data[1] >>= 8; - data[2] >>= 8; - data[3] >>= 8; - - // cc (64bit = 8バイト) は各バイトが各ピクセルの - // カラーインデックスになるので、 - // BitmapI8 に 8バイト一気に書き込める。 - // (エンディアンの影響は deptable[] で処理してある) - uint64 cc = p0 | (p1 << 1) | (p2 << 2) | (p3 << 3); - *dst++ = cc; - } + // 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; } } } @@ -254,183 +219,146 @@ PlaneVRAMDevice::RenderCompositeToRGB(Bi const int width = bitmap.GetWidth(); const int height = bitmap.GetHeight(); - // view_mod は表示領域 view における更新矩形 + // view_mod は表示領域 view における更新ラスタ BitmapI8 view(width, height); - std::vector view_mod(height / BLKY); + std::vector view_mod(height); // この view に対する modify を計算する - uint16 view_mask = ~(0xffff >> (width / BLKX)); - view_mask &= modify_mask; if (__predict_false(modify.invalidate2)) { - std::fill(view_mod.begin(), view_mod.end(), view_mask); + std::fill(view_mod.begin(), view_mod.end(), 1); } else { // Y scroll 方向の modify 計算 - int sy = yscroll / BLKY; - for (int y = 0; y < view_mod.size(); y++, sy++) { - if (sy >= modify.bits.size()) { - sy = 0; + int sy = yscroll; + for (int y = 0; y < height; y++, sy++) { + if (sy >= composite.GetHeight()) { + sy -= composite.GetHeight(); } view_mod[y] = modify.bits[sy]; } - if (yscroll % BLKY != 0) { - uint16 mod0 = view_mod[0]; - for (int y = 0; y < view_mod.size() - 1; y++) { - view_mod[y] |= view_mod[y + 1]; - } - view_mod[view_mod.size() - 1] |= mod0; - } - // X scroll 方向の modify 計算 - int n = xscroll / BLKX; - int f = xscroll % BLKX; - for (int y = 0; y < view_mod.size(); y++) { - uint16 m = view_mod[y] << n; - if (f != 0) { - m |= m << 1; + // 右がはみ出す場合は、はみ出した先のラスタも加味する。 + 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] |= modify.bits[sy2]; } - m &= view_mask; - view_mod[y] = m; - // TODO: はみ出しの折り返し } } // composite からスクロールを加味した表示領域 view を作る - for (int by = 0; by < view_mod.size(); by++) { - uint16 mod = view_mod[by]; - - for (int bx = 0; mod != 0; bx++, mod <<= 1) { - if ((int16)mod >= 0) { - continue; - } + int sy = yscroll; + for (int y = 0; y < height; y++, sy++) { + if (sy >= composite.GetHeight()) { + sy -= composite.GetHeight(); + } + if (view_mod[y] == 0) { + continue; + } - int vy = by * BLKY; - int sy = yscroll + by * BLKY; - for (int y = 0; y < BLKY; y++, vy++, sy++) { - if (sy >= composite.GetHeight()) { - sy -= composite.GetHeight(); - } - int vx = bx * BLKX; - int sx = xscroll + bx * BLKX; - uint8 *v = view.GetPtr(vx, vy); - uint8 *c = composite.GetPtr(sx, sy); - - if (sx + BLKX < composite.GetWidth()) { - for (int x = 0; x < BLKX; x++) { - *v++ = *c++; - } - } else { - int x; - for (x = sx; x < composite.GetWidth(); x++) { - *v++ = *c++; - } - - int sy2 = sy + 256; - if (sy2 >= composite.GetHeight()) { - sy2 -= composite.GetHeight(); - } - c = composite.GetPtr(0, sy2); - - for (; x < BLKX; x++) { - *v++ = *c++; - } - } - } + uint8 *v = view.GetPtr(0, 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.GetPtr(0, sy2); + int len2 = view.GetWidth() - len1; + memcpy(v, c, len2); } } // view (I8) を Bitmap (RGB) に展開する - for (int by = 0; by < view_mod.size(); by++) { - uint16 mod = view_mod[by]; + for (int y = 0; y < view_mod.size(); y++) { + if (view_mod[y] == 0) { + continue; + } + + const uint8 *s8 = view.GetPtr(0, y); + uint32 *d = (uint32 *)bitmap.GetPtr(0, y); - for (int bx = 0; mod != 0; bx++, mod <<= 1) { - if ((int16)mod < 0) { - // 変更のあった1ブロックを更新 - int x = bx * BLKX; - - int vy = by * BLKY; - for (int y = 0; y < BLKY; y++, vy++) { - - const uint8 *s8 = view.GetPtr(x, vy); - uint32 *d = (uint32 *)bitmap.GetPtr(x, vy); - - const uint32 *s = (const uint32 *)s8; - const uint32 *send = (const uint32 *)(s8 + BLKX); - for (; s < send;) { - // s は BitmapI8 なのでビッグエンディアン並びに相当 + const uint32 *s = (const uint32 *)s8; + const uint32 *send = (const uint32 *)(s8 + view.GetWidth()); + for (; s < send;) { + // s は BitmapI8 なのでビッグエンディアン並びに相当 #if defined(__AVX2__) && !defined(NO_AVX) // オプション -mavx2 が必要 - // 35.1msec + // 35.1msec - // 8ピクセルずつ処理する + // 8ピクセルずつ処理する - // idx0 = $00000000'00000000'I7I6I5I4'I3I2I1I0 - __m128i idx0 = _mm_loadl_epi64((const __m128i *)s); - s += 2; - - // idx = $000000I7'000000I6'000000I5'000000I4' - // 000000I3'000000I2'000000I1'000000I0 - __m256i idx = _mm256_cvtepu8_epi32(idx0); - - // 8個の 32bit インデックスから XBGR を取得 - // c = $00B7G7R7'00B6G6R6'00B5G5R5'00B4G4R4' - // 00B3G3R3'00B2G2R2'00B1G1R1'00B0G0R0 - __m256i c = _mm256_i32gather_epi32( - (const int *)palette, idx, 4); - - // 256bit リニアに使えれば下詰めで済んだんだが、 - // 上位 128bit と下位 128bit をまたいでシャッフルは - // 出来なくて、かつこの後のストアは連続領域への - // 書き込みになるので、中央でつなげておく。うーん。 - // c = $00000000'B7G7R7B6'G6R6B5G5'R5B4G4R4' - // B3G3R3B2'G2R2B1G1'R1B0G0R0'00000000 - __m256i shuff = _mm256_set_epi8( - -1,-1,-1,-1, 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0, - 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0, -1,-1,-1,-1 - ); - __m256i res = _mm256_shuffle_epi8(c, shuff); - - // 先頭(下位)と末尾(上位)の 32bit ずつを除いたところを - // 書き出す。 - __m256i mask = _mm256_set_epi32( - 0, -1, -1, -1, -1, -1, -1, 0 - ); - _mm256_maskstore_epi32((int *)(d - 1), mask, res); - d += 6; + // idx0 = $00000000'00000000'I7I6I5I4'I3I2I1I0 + __m128i idx0 = _mm_loadl_epi64((const __m128i *)s); + s += 2; + + // idx = $000000I7'000000I6'000000I5'000000I4' + // 000000I3'000000I2'000000I1'000000I0 + __m256i idx = _mm256_cvtepu8_epi32(idx0); + + // 8個の 32bit インデックスから XBGR を取得 + // c = $00B7G7R7'00B6G6R6'00B5G5R5'00B4G4R4' + // 00B3G3R3'00B2G2R2'00B1G1R1'00B0G0R0 + __m256i c = _mm256_i32gather_epi32( + (const int *)palette, idx, 4); + + // 256bit リニアに使えれば下詰めで済んだんだが、 + // 上位 128bit と下位 128bit をまたいでシャッフルは + // 出来なくて、かつこの後のストアは連続領域への + // 書き込みになるので、中央でつなげておく。うーん。 + // c = $00000000'B7G7R7B6'G6R6B5G5'R5B4G4R4' + // B3G3R3B2'G2R2B1G1'R1B0G0R0'00000000 + __m256i shuff = _mm256_set_epi8( + -1,-1,-1,-1, 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0, + 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0, -1,-1,-1,-1 + ); + __m256i res = _mm256_shuffle_epi8(c, shuff); + + // 先頭(下位)と末尾(上位)の 32bit ずつを除いたところを + // 書き出す。 + __m256i mask = _mm256_set_epi32( + 0, -1, -1, -1, -1, -1, -1, 0 + ); + _mm256_maskstore_epi32((int *)(d - 1), mask, res); + d += 6; #else - // 46.5msec + // 46.5msec - // 4ピクセルずつ処理する + // 4ピクセルずつ処理する - uint32 cc4 = *s++; - // 左側ピクセルが下位バイトになるよう並び替える - // (le32toh() はビッグエンディアンなら bswap32 の意) - cc4 = le32toh(cc4); - - // 4ピクセルをそれぞれ xBGR32 に変換 - uint32 c0 = palette[ cc4 & 0xff].xbgr; - uint32 c1 = palette[(cc4 >> 8) & 0xff].xbgr; - uint32 c2 = palette[(cc4 >> 16) & 0xff].xbgr; - uint32 c3 = palette[ cc4 >> 24 ].xbgr; - - // xBGR32 4つ(16バイト)を RGB24 3つ(12バイト)に変換して - // ロングワードで書き出す。 - // c0 = $x0B0G0R0 - // c1 = $x1B1G1R1 - // c2 = $x2B2G2R2 - // c3 = $x3B3G3R3 - // - // d[0] = $00B0G0R0 | $R1000000 = $R1B0G0R0 - // d[1] = $0000B1G1 | $G2R20000 = $G2R2B1G1 - // d[2] = $000000B2 | $B3G3R300 = $B3G3R3B2 - - // (le32toh() はビッグエンディアンなら bswap32 の意) - *d++ = le32toh((c0 ) | (c1 << 24)); - *d++ = le32toh((c1 >> 8) | (c2 << 16)); - *d++ = le32toh((c2 >> 16) | (c3 << 8)); + uint32 cc4 = *s++; + // 左側ピクセルが下位バイトになるよう並び替える + // (le32toh() はビッグエンディアンなら bswap32 の意) + cc4 = le32toh(cc4); + + // 4ピクセルをそれぞれ xBGR32 に変換 + uint32 c0 = palette[ cc4 & 0xff].xbgr; + uint32 c1 = palette[(cc4 >> 8) & 0xff].xbgr; + uint32 c2 = palette[(cc4 >> 16) & 0xff].xbgr; + uint32 c3 = palette[ cc4 >> 24 ].xbgr; + + // xBGR32 4つ(16バイト)を RGB24 3つ(12バイト)に変換して + // ロングワードで書き出す。 + // c0 = $x0B0G0R0 + // c1 = $x1B1G1R1 + // c2 = $x2B2G2R2 + // c3 = $x3B3G3R3 + // + // d[0] = $00B0G0R0 | $R1000000 = $R1B0G0R0 + // d[1] = $0000B1G1 | $G2R20000 = $G2R2B1G1 + // d[2] = $000000B2 | $B3G3R300 = $B3G3R3B2 + + // (le32toh() はビッグエンディアンなら bswap32 の意) + *d++ = le32toh((c0 ) | (c1 << 24)); + *d++ = le32toh((c1 >> 8) | (c2 << 16)); + *d++ = le32toh((c2 >> 16) | (c3 << 8)); #endif - } - } - } } } }