|
|
1.1 root 1: //
2: // nono
1.1.1.3 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
5: //
6:
1.1 root 7: //
8: // TVRAM
9: //
10:
11: #include "tvram.h"
1.1.1.3 root 12: #include "crtc.h"
13: #include "renderer.h"
14: #include "videoctlr.h"
1.1.1.4 ! root 15: #if defined(__x86_64__)
! 16: #include <x86intrin.h>
! 17: #endif
! 18:
! 19: #if defined(PERF)
! 20: // テキスト画面合成のパフォーマンス測定。PERF をどっかで有効にしてビルド。
! 21: // X68030 IPLROM で起動してくるあたりの画面更新で測定する。
! 22: #include <chrono>
! 23: static int total_elapsed;
! 24: static int total_count;
! 25: static std::list<int> fpsarray;
! 26: #endif
1.1 root 27:
28: //
29: // TVRAM はワード単位でホストバイトオーダ配置なので、
30: // バイトアクセス時は HB() マクロを使用のこと。
31: //
32:
1.1.1.2 root 33: std::unique_ptr<TVRAMDevice> gTVRAM;
1.1 root 34:
35: TVRAMDevice::TVRAMDevice()
36: {
37: logname = "tvram";
38: devname = "TVRAM";
39: devaddr = baseaddr;
40: devlen = 512 * 1024;
41:
1.1.1.2 root 42: mem.reset(new uint8[devlen]);
1.1.1.4 ! root 43:
! 44: // レンダリング時に使うテーブルを事前に計算。
! 45: // %abcdefgh の8ビットを
! 46: // %000h000g'000f000e'000d000c'000b000a
! 47: // の32ビットに伸張する。(各 nibble の最下位ビットに対応させる)
! 48: for (int i = 0; i < 256; i++) {
! 49: uint32_t res = 0;
! 50: for (int bit = 0; bit < 8; bit++) {
! 51: res |= ((i >> bit) & 1) << (28 - bit * 4);
! 52: }
! 53: deptable[i] = res;
! 54: }
1.1 root 55: }
56:
57: TVRAMDevice::~TVRAMDevice()
58: {
59: }
60:
61: void
62: TVRAMDevice::ResetHard()
63: {
64: // XXX ここ?
65: Invalidate();
1.1.1.4 ! root 66:
! 67: #if defined(PERF)
! 68: // 分かりやすさのために表示
! 69: printf("measuring TVRAM performance...\n");
! 70: #endif
1.1 root 71: }
72:
1.1.1.2 root 73: inline uint64
74: TVRAMDevice::Decoder(uint32 addr) const
75: {
76: return addr - baseaddr;
77: }
78:
1.1 root 79: uint64
80: TVRAMDevice::Read8(uint32 addr)
81: {
1.1.1.2 root 82: uint32 offset = Decoder(addr);
1.1 root 83: return mem[HB(offset)];
84: }
85:
86: uint64
87: TVRAMDevice::Read16(uint32 addr)
88: {
1.1.1.2 root 89: uint32 offset = Decoder(addr);
1.1 root 90: return *(uint16 *)&mem[offset];
91: }
92:
93: uint64
94: TVRAMDevice::Write8(uint32 addr, uint32 data)
95: {
1.1.1.2 root 96: uint32 offset = Decoder(addr);
1.1 root 97:
98: // XXX とりあえずね
99: putlog(3, "Write $%06x <- $%02x", addr, data);
100:
101: if (tvram.sa) {
102: // 同時アクセス
103: uint32 poffset = offset & 0x1ffff;
104: for (int plane = 0; plane < 4; plane++) {
105: if (tvram.ap[plane]) {
106: mem[HB(0x20000 * plane + poffset)] = data;
107: }
108: }
109: } else {
110: // 通常アクセス
111: mem[HB(offset)] = data;
112: }
113: atomic_dirty[(offset % 0x20000) / 128] = 1;
114: return 0;
115: }
116:
117: uint64
118: TVRAMDevice::Write16(uint32 addr, uint32 data)
119: {
1.1.1.2 root 120: uint32 offset = Decoder(addr);
1.1 root 121:
122: // XXX とりあえずね
123: putlog(3, "Write $%06x <- $%04x", addr, data);
124:
125: if (tvram.sa) {
126: // 同時アクセス
127: uint32 poffset = offset & 0x1ffff;
128: for (int plane = 0; plane < 4; plane++) {
129: if (tvram.ap[plane]) {
130: *(uint16 *)&mem[0x20000 * plane + poffset] = data;
131: }
132: }
133: } else {
134: // 通常アクセス
135: *(uint16 *)&mem[offset] = data;
136: }
137: atomic_dirty[(offset % 0x20000) / 128] = 1;
138: return 0;
139: }
140:
141: uint64
142: TVRAMDevice::Peek8(uint32 addr)
143: {
1.1.1.2 root 144: uint32 offset = Decoder(addr);
145: return mem[HB(offset)];
1.1 root 146: }
147:
148: // 全画面の更新フラグを立てる
149: void
150: TVRAMDevice::Invalidate()
151: {
152: for (int i = 0; i < 1024; i++) {
153: atomic_dirty[i] = 1;
154: }
155: }
156:
1.1.1.3 root 157: void
158: TVRAMDevice::SetScrollX(int x)
159: {
160: tvram.xscroll = x;
161: }
162:
163: void
164: TVRAMDevice::SetScrollY(int y)
165: {
166: tvram.yscroll = y;
167: // 全ラインを無効にする XXX とりあえずね
168: Invalidate();
169: }
170:
1.1 root 171: // CRTC R21 での同時アクセス設定を反映する
172: // CRTC から呼ばれる
173: void
174: TVRAMDevice::set_crtc_21(uint16 data)
175: {
176: tvram.sa = (data & 0x100);
177: tvram.ap[0] = (data & 0x10);
178: tvram.ap[1] = (data & 0x20);
179: tvram.ap[2] = (data & 0x40);
180: tvram.ap[3] = (data & 0x80);
181:
182: if (0) {
183: if (tvram.sa) {
184: putlog(2, "テキスト画面同時アクセス有効 %c%c%c%c",
185: tvram.ap[0] ? '0' : '-',
186: tvram.ap[1] ? '1' : '-',
187: tvram.ap[2] ? '2' : '-',
188: tvram.ap[3] ? '3' : '-');
189: } else {
190: putlog(2, "テキスト画面同時アクセス無効");
191: }
192: }
193: }
194:
195: // テキスト画面合成
196: bool
197: TVRAMDevice::Render(uint8 *imagebuf)
198: {
1.1.1.4 ! root 199: const uint32 *pal;
1.1 root 200: const uint16 *plane0;
201: const uint16 *plane1;
202: const uint16 *plane2;
203: const uint16 *plane3;
204: uint8 *dst0;
205: bool updated;
206:
1.1.1.4 ! root 207: #if defined(PERF)
! 208: auto start = std::chrono::system_clock::now();
! 209: #endif
! 210:
1.1 root 211: dst0 = imagebuf;
212:
1.1.1.4 ! root 213: // 加工済みパレットを取得
! 214: pal = gVideoCtlr->GetCookedPalette();
1.1 root 215:
216: updated = false;
217:
218: // dirty は VRAM への書き込みがあれば常に 1 にする。
219: // レンダリング時に dirty が 1 なら 2 にしてレンダリング。
220: // レンダリング終了時に dirty が 2 なら 0 に戻す。
221: // これによりレンダリング中に VRAM への書き込みがあれば次回描画される。
222:
223: // sy はメモリ上の Y 座標
224: // dy はレンダラ画面の Y 座標 (CRTC のXXX を考慮)
225: // XXX スクロールは未実装
226:
227: // テキスト画面
1.1.1.2 root 228: plane0 = (uint16 *)&mem[0x00000];
229: plane1 = (uint16 *)&mem[0x20000];
230: plane2 = (uint16 *)&mem[0x40000];
231: plane3 = (uint16 *)&mem[0x60000];
1.1.1.3 root 232: int sy = tvram.yscroll;
1.1 root 233: for (int dy = 0; dy < 512; dst0 += 768 * Renderer::BPP, dy++, sy++) {
234: sy &= 0x3ff;
235:
1.1.1.4 ! root 236: #if defined(PERF)
! 237: // 計測中は常に全域を展開。
! 238: // 起動直後のタイトルが消えるくらいまでではほぼ更新がなく
! 239: // ほぼ atomic_cas の時間を測定してるだけになってしまうので。
! 240: atomic_dirty[sy] = 2;
! 241: if (1) {
! 242: #else
1.1 root 243: if (atomic_cas_8(&atomic_dirty[sy], 1, 2) == 1) {
1.1.1.4 ! root 244: #endif
! 245: uint32 *dst = (uint32 *)dst0;
! 246: uint32 *prb = rb;
! 247:
1.1 root 248: // sx はメモリ上の X オフセット (ワード単位)
249: // dx はレンダラ画面の X オフセット (ワード単位)
250:
251: int sx = sy * 1024 / 16;
252: for (int dx = 0; dx < 768 / 16; dx++, sx++) {
1.1.1.4 ! root 253: #if defined(__SSE2__) && !defined(NO_SSE)
! 254: __m128i a;
! 255: // 各プレーン 16 ビットを DWORD * 4 に読み込む。
! 256: // plane0 = A, plane1 = B, plane2 = C, plane3 = D とすると
! 257: // a = $0000DDDD'0000CCCC'0000BBBB'0000AAAA;
! 258: a = _mm_set_epi32(
! 259: plane3[sx],
! 260: plane2[sx],
! 261: plane1[sx],
! 262: plane0[sx]);
! 263: // 各DWORD中の上位ワードに移動
! 264: // a = $DDDD0000'CCCC0000'BBBB0000'AAAA0000;
! 265: a = _mm_slli_si128(a, 2);
! 266: for (int j = 0; j < 16; j++) {
! 267: // 各DWORDの最上位ビットを計4ビット取り出す
! 268: int c = _mm_movemask_ps(_mm_castsi128_ps(a));
! 269: // 各DWORDを左シフト
! 270: a = _mm_add_epi32(a, a);
! 271: *prb++ = pal[c];
! 272: }
! 273: #else
! 274: // t0..t3 は 16 ドット× 4 プレーン分の TVRAM 生データ。
! 275: // このうち先頭の2ドットのカラーコードが %abcd, %efgh で
! 276: // 表される場合、t0..t3 は以下のようになっている。
! 277: // t0 = %dhxxxxxx'xxxxxxxx
! 278: // t1 = %cgxxxxxx'xxxxxxxx
! 279: // t2 = %bfxxxxxx'xxxxxxxx
! 280: // t3 = %aexxxxxx'xxxxxxxx
! 281: //
! 282: // deptable[] によってこの t0..t3 の 16ビットを
! 283: // d0..d3 の 64ビット(16 nibble) に展開する。
! 284: // d0 = %000x'000x' .. '000h'000d
! 285: // d1 = %000x'000x' .. '000g'000c
! 286: // d2 = %000x'000x' .. '000f'000b
! 287: // d3 = %000x'000x' .. '000e'000a
! 288: //
! 289: // これを 1ビットずつずらして合成する。
! 290: // cc = %xxxx'xxxx' .. 'efgh'abcd
! 291: //
! 292: // 16進数で表すと
! 293: // cc = $PONMLKJI'HGFEDCBA で
! 294: // $A が +0 ドット目のカラーコード、
! 295: // $B が +1 ドット目のカラーコード、
! 296: // :
! 297: // $P が +15ドット目のカラーコード、
! 298: // となる。
! 299:
1.1 root 300: uint16 t0 = plane0[sx];
301: uint16 t1 = plane1[sx];
302: uint16 t2 = plane2[sx];
303: uint16 t3 = plane3[sx];
304:
1.1.1.4 ! root 305: union64 d0, d1, d2, d3;
! 306: d0.l = deptable[t0 >> 8];
! 307: d1.l = deptable[t1 >> 8];
! 308: d2.l = deptable[t2 >> 8];
! 309: d3.l = deptable[t3 >> 8];
! 310: d0.h = deptable[t0 & 0xff];
! 311: d1.h = deptable[t1 & 0xff];
! 312: d2.h = deptable[t2 & 0xff];
! 313: d3.h = deptable[t3 & 0xff];
! 314:
! 315: uint64 cc = d0.q | (d1.q << 1) | (d2.q << 2) | (d3.q << 3);
! 316:
1.1 root 317: for (int j = 0; j < 16; j++) {
1.1.1.4 ! root 318: uint32 c = cc & 0xf;
! 319: *prb++ = pal[c];
! 320: cc >>= 4;
1.1 root 321: }
1.1.1.4 ! root 322: #endif
1.1 root 323: }
324:
1.1.1.4 ! root 325: #if defined(__SSE4_1__) && !defined(NO_SSE)
! 326: // XBGR32 16ピクセル (16 ロングワード = 64バイト) から
! 327: // RGB24 16ピクセル (12 ロングワード = 48バイト) に変換する
! 328: for (int j = 0; j < 768; j += 16) {
! 329: // rb[0,1,2,3] = $00B0G0R0,$00B1G1R1,$00B2G2R2,$00B3G3R3
! 330: // なので LE で 128ビット値としてレジスタに読み込むと
! 331: // a[0] = $00B3G3R3'00B2G2R2'00B1G1R1'00B0G0R0 となる。
! 332: //
! 333: // ここは1文字が1バイトで x111 = $X1B1G1R1 だと思いねえ。
! 334: // 入力の rb[0..15] と a[0..3] (どちらも64バイト) はこう。
! 335: // rb[0,1,2,3] = a[0] = x333 x222 x111 x000
! 336: // rb[4,5,6,7] = a[1] = x777 x666 x555 x444
! 337: // rb[8,9,a,b] = a[2] = xbbb xaaa x999 x888
! 338: // rb[c,d,e,f] = a[3] = xfff xeee xddd xccc
! 339: //
! 340: // suff(shuffle index) を使って a[0..3] を b[0..3] にする。
! 341: // この次のステージで OR しやすい順を意識して並べ替える。
! 342: // **** は使わないところ。
! 343: // b[0] = **** 3332 2211 1000
! 344: // b[1] = 5444 **** 7776 6655
! 345: // b[2] = aa99 9888 **** bbba
! 346: // b[3] = fffe eedd dccc ****
! 347: //
! 348: // {P,Q,R,S} 表記を4ロングワードそれぞれに対するマスクとして、
! 349: // c[0] = (b[0] & {0,1,1,1}) | (b[1] & {1,0,0,0})
! 350: // c[1] = (b[1] & {0,0,1,1}) | (b[2] & {1,1,0,0})
! 351: // c[2] = (b[2] & {0,0,0,1}) | (b[3] & {1,1,1,0})
! 352: // で
! 353: // c[0] = 5444 3332 2211 1000
! 354: // c[1] = aa99 9888 7776 6655
! 355: // c[2] = fffe eedd dccc bbba
! 356: // ができる。
! 357:
! 358: __m128i suff0 = _mm_set_epi8(
! 359: -1,-1,-1,-1, 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0);
! 360: __m128i suff1 = _mm_set_epi8(
! 361: 4, 2, 1, 0, -1,-1,-1,-1, 14,13,12,10, 9, 8, 6, 5);
! 362: __m128i suff2 = _mm_set_epi8(
! 363: 9, 8, 6, 5, 4, 2, 1, 0, -1,-1,-1,-1, 14,13,12,10);
! 364: __m128i suff3 = _mm_set_epi8(
! 365: 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0, -1,-1,-1,-1);
! 366:
! 367: __m128i a0 = _mm_load_si128((__m128i *)(&rb[j + 0]));
! 368: __m128i a1 = _mm_load_si128((__m128i *)(&rb[j + 4]));
! 369: __m128i a2 = _mm_load_si128((__m128i *)(&rb[j + 8]));
! 370: __m128i a3 = _mm_load_si128((__m128i *)(&rb[j + 12]));
! 371:
! 372: __m128i b0 = _mm_shuffle_epi8(a0, suff0);
! 373: __m128i b1 = _mm_shuffle_epi8(a1, suff1);
! 374: __m128i b2 = _mm_shuffle_epi8(a2, suff2);
! 375: __m128i b3 = _mm_shuffle_epi8(a3, suff3);
! 376:
! 377: #define PS(x) _mm_castsi128_ps(x)
! 378: #define SI(x) _mm_castps_si128(x)
! 379: __m128i c0 = SI(_mm_blend_ps(PS(b0), PS(b1), 0b1000));
! 380: __m128i c1 = SI(_mm_blend_ps(PS(b1), PS(b2), 0b1100));
! 381: __m128i c2 = SI(_mm_blend_ps(PS(b2), PS(b3), 0b1110));
! 382:
! 383: _mm_storeu_si128((__m128i *)(&dst[0]), c0);
! 384: _mm_storeu_si128((__m128i *)(&dst[4]), c1);
! 385: _mm_storeu_si128((__m128i *)(&dst[8]), c2);
! 386: dst += 12;
! 387: }
! 388: #else
! 389: // xBGR32 4つ(16バイト)を RGB24 3つ(12バイト)に変換して
! 390: // ロングワードで書き出す。
! 391: for (int j = 0; j < 768; j += 4) {
! 392: // rb[0] = $x0B0G0R0
! 393: // rb[1] = $x1B1G1R1
! 394: // rb[2] = $x2B2G2R2
! 395: // rb[3] = $x3B3G3R3
! 396: //
! 397: // dst[0] = $00B0G0R0 | $R1000000 = $R1B0G0R0
! 398: // dst[1] = $0000B1G1 | $G2R20000 = $G2R2B1G1
! 399: // dst[2] = $000000B2 | $B3G3R300 = $B3G3R3B2
! 400:
! 401: *dst++ = le32toh((rb[j + 0] ) | (rb[j + 1] << 24));
! 402: *dst++ = le32toh((rb[j + 1] >> 8) | (rb[j + 2] << 16));
! 403: *dst++ = le32toh((rb[j + 2] >> 16) | (rb[j + 3] << 8));
! 404: }
! 405: #endif
! 406:
1.1 root 407: // 2の時だけ0に戻す
408: atomic_cas_8(&atomic_dirty[sy], 2, 0);
409: updated = true;
410: }
411: }
412:
1.1.1.4 ! root 413: #if defined(PERF)
! 414: auto end = std::chrono::system_clock::now();
! 415: total_elapsed += std::chrono::duration_cast<std::chrono::microseconds>
! 416: (end - start).count();
! 417: if (++total_count >= 100) {
! 418: int fps = 1000 * 1000 / (total_elapsed / total_count);
! 419: total_elapsed = 0;
! 420: total_count = 0;
! 421: printf("%d fps", fps);
! 422: fpsarray.push_back(fps);
! 423: if (fpsarray.size() > 4) {
! 424: fpsarray.pop_front();
! 425: }
! 426: if (fpsarray.size() > 2) {
! 427: int minv = 1e9;
! 428: int sum = 0;
! 429: for (auto v : fpsarray) {
! 430: if (v < minv)
! 431: minv = v;
! 432: sum += v;
! 433: }
! 434: // 最悪値1つを除いた移動平均
! 435: sum -= minv;
! 436: int avg = sum / (fpsarray.size() - 1);
! 437: printf(" (avg %d)", avg);
! 438: }
! 439: printf("\n");
! 440: }
! 441: #endif
! 442:
1.1 root 443: return updated;
444: }
1.1.1.4 ! root 445:
! 446: // スタティック変数
! 447: /*static*/ uint32 TVRAMDevice::deptable[256];
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.