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