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