326 lines
7.9 KiB
TypeScript
326 lines
7.9 KiB
TypeScript
|
|
// GTIA
|
|
// https://user.xmission.com/~trevin/atari/gtia_regs.html
|
|
// https://user.xmission.com/~trevin/atari/gtia_pinout.html
|
|
|
|
import { dumpRAM } from "../../common/emu";
|
|
import { hex, rgb2bgr, safe_extend } from "../../common/util";
|
|
|
|
|
|
// write regs
|
|
const HPOSP0 = 0x0;
|
|
const HPOSM0 = 0x4;
|
|
const SIZEP0 = 0x8;
|
|
const SIZEM = 0x0c;
|
|
const GRAFP0 = 0x0d;
|
|
const GRAFM = 0x11;
|
|
const COLPM0 = 0x12;
|
|
const COLPF0 = 0x16;
|
|
const COLPF1 = 0x17;
|
|
const COLPF2 = 0x18;
|
|
const COLPF3 = 0x19;
|
|
const COLBK = 0x1a;
|
|
const PRIOR = 0x1b;
|
|
const VDELAY = 0x1c;
|
|
const GRACTL = 0x1d;
|
|
const HITCLR = 0x1e;
|
|
const CONSPK = 0x1f;
|
|
// read regs
|
|
const M0PF = 0x0;
|
|
const P0PF = 0x4;
|
|
const M0PL = 0x8;
|
|
const P0PL = 0xc;
|
|
export const TRIG0 = 0x10;
|
|
export const CONSOL = 0x1f;
|
|
|
|
export class GTIA {
|
|
regs = new Uint8Array(0x20);
|
|
readregs = new Uint8Array(0x20);
|
|
shiftregs = new Uint32Array(8);
|
|
|
|
count = 0;
|
|
an = 0;
|
|
rgb = 0;
|
|
pmcol = 0;
|
|
|
|
reset() {
|
|
this.regs.fill(0);
|
|
this.readregs.fill(0); // TODO?
|
|
this.count = 0;
|
|
}
|
|
saveState() {
|
|
return safe_extend(0, {}, this);
|
|
}
|
|
loadState(s) {
|
|
safe_extend(0, this, s);
|
|
}
|
|
setReg(a: number, v: number) {
|
|
switch (a) {
|
|
case CONSOL:
|
|
v = (v & 15) ^ 15; // 0 = input, 1 = pull down
|
|
break;
|
|
case HITCLR:
|
|
for (let i = 0; i < 16; i++) {
|
|
this.readregs[i] = 0;
|
|
}
|
|
return;
|
|
}
|
|
this.regs[a] = v;
|
|
}
|
|
readReg(a: number) {
|
|
return this.readregs[a];
|
|
}
|
|
updateGfx(h: number, data: number) {
|
|
switch (h) {
|
|
case 0:
|
|
this.count = 0;
|
|
if (this.regs[GRACTL] & 1) { this.regs[GRAFM] = data; }
|
|
break;
|
|
case 2: case 3: case 4: case 5:
|
|
if (this.regs[GRACTL] & 2) { this.regs[GRAFP0 - 2 + h] = data; }
|
|
break;
|
|
}
|
|
}
|
|
getPlayfieldColor(): number {
|
|
switch (this.an) {
|
|
case 0:
|
|
return COLBK;
|
|
case 4: case 5: case 6: case 7:
|
|
return COLPF0 + this.an - 4;
|
|
case 8:
|
|
// combine PF2 hue and PF1 luminance
|
|
return (this.regs[COLPF2] & 0xf0) | (this.regs[COLPF1] & 0x0f) | 0x100;
|
|
}
|
|
return 0x100; // black
|
|
}
|
|
clockPulse1(): void {
|
|
let topobj = -1;
|
|
let pfset = this.an - 4; // TODO?
|
|
let ppmask = 0;
|
|
// players
|
|
for (let i = 0; i < 4; i++) {
|
|
let bit = this.shiftObject(i);
|
|
if (bit) {
|
|
if (pfset >= 0) {
|
|
this.readregs[P0PF + i] |= 1 << pfset;
|
|
}
|
|
ppmask |= 1 << i;
|
|
topobj = i;
|
|
}
|
|
}
|
|
this.readregs[P0PL + 0] |= ppmask & ~1;
|
|
this.readregs[P0PL + 1] |= ppmask & ~2;
|
|
this.readregs[P0PL + 2] |= ppmask & ~4;
|
|
this.readregs[P0PL + 3] |= ppmask & ~8;
|
|
// missiles
|
|
for (let i = 0; i < 4; i++) {
|
|
let bit = this.shiftObject(i + 4);
|
|
if (bit) {
|
|
if (pfset >= 0) {
|
|
this.readregs[M0PF + i] |= 1 << pfset;
|
|
}
|
|
this.readregs[M0PL + i] |= ppmask;
|
|
topobj = i + 4;
|
|
}
|
|
}
|
|
this.pmcol = topobj >= 0 ? this.getObjectColor(topobj) : -1;
|
|
this.count++;
|
|
this.clockPulse2();
|
|
}
|
|
clockPulse2(): void {
|
|
var col: number;
|
|
if (this.pmcol >= 0) {
|
|
col = this.pmcol;
|
|
} else {
|
|
let pf = this.getPlayfieldColor();
|
|
col = pf & 0x100 ? pf & 0xff : this.regs[pf];
|
|
}
|
|
this.rgb = COLORS_RGBA[col];
|
|
}
|
|
shiftObject(i: number) {
|
|
let bit = this.shiftregs[i] & 0x80000000;
|
|
this.shiftregs[i] <<= 1;
|
|
if (this.regs[HPOSP0 + i] - 7 == this.count) {
|
|
this.triggerObject(i);
|
|
}
|
|
return bit;
|
|
}
|
|
getObjectColor(i: number) {
|
|
if ((this.regs[PRIOR] & 0x10) && i >= 4) {
|
|
return this.regs[COLPF3];
|
|
} else {
|
|
return this.regs[COLPM0 + (i & 3)];
|
|
}
|
|
}
|
|
triggerObject(i: number) {
|
|
let size, data;
|
|
if (i < 4) {
|
|
size = this.regs[SIZEP0 + i] & 3;
|
|
data = this.regs[GRAFP0 + i];
|
|
} else {
|
|
let s = (i - 4) << 1;
|
|
size = (this.regs[SIZEM] >> s) & 3;
|
|
data = ((this.regs[GRAFM] >> s) & 3) << 6;
|
|
}
|
|
if (size & 1) data = expandBits(data); else data <<= 8;
|
|
if (size == 3) data = expandBits(data); else data <<= 16;
|
|
this.shiftregs[i] = data;
|
|
}
|
|
|
|
static stateToLongString(state): string {
|
|
let s = ''
|
|
s += "Write Registers:\n";
|
|
s += dumpRAM(state.regs, 0, 32);
|
|
s += "Read Registers:\n";
|
|
s += dumpRAM(state.readregs, 0, 32);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
function expandBits(x: number): number {
|
|
x = (x | (x << 8)) & 0x00FF00FF;
|
|
x = (x | (x << 4)) & 0x0F0F0F0F;
|
|
x = (x | (x << 2)) & 0x33333333;
|
|
x = (x | (x << 1)) & 0x55555555;
|
|
return x | (x << 1);
|
|
}
|
|
|
|
const ATARI_NTSC_RGB = [
|
|
0x000000, // 00
|
|
0x404040, // 02
|
|
0x6c6c6c, // 04
|
|
0x909090, // 06
|
|
0xb0b0b0, // 08
|
|
0xc8c8c8, // 0A
|
|
0xdcdcdc, // 0C
|
|
0xf4f4f4, // 0E
|
|
0x004444, // 10
|
|
0x106464, // 12
|
|
0x248484, // 14
|
|
0x34a0a0, // 16
|
|
0x40b8b8, // 18
|
|
0x50d0d0, // 1A
|
|
0x5ce8e8, // 1C
|
|
0x68fcfc, // 1E
|
|
0x002870, // 20
|
|
0x144484, // 22
|
|
0x285c98, // 24
|
|
0x3c78ac, // 26
|
|
0x4c8cbc, // 28
|
|
0x5ca0cc, // 2A
|
|
0x68b4dc, // 2C
|
|
0x78c8ec, // 2E
|
|
0x001884, // 30
|
|
0x183498, // 32
|
|
0x3050ac, // 34
|
|
0x4868c0, // 36
|
|
0x5c80d0, // 38
|
|
0x7094e0, // 3A
|
|
0x80a8ec, // 3C
|
|
0x94bcfc, // 3E
|
|
0x000088, // 40
|
|
0x20209c, // 42
|
|
0x3c3cb0, // 44
|
|
0x5858c0, // 46
|
|
0x7070d0, // 48
|
|
0x8888e0, // 4A
|
|
0xa0a0ec, // 4C
|
|
0xb4b4fc, // 4E
|
|
0x5c0078, // 50
|
|
0x74208c, // 52
|
|
0x883ca0, // 54
|
|
0x9c58b0, // 56
|
|
0xb070c0, // 58
|
|
0xc084d0, // 5A
|
|
0xd09cdc, // 5C
|
|
0xe0b0ec, // 5E
|
|
0x780048, // 60
|
|
0x902060, // 62
|
|
0xa43c78, // 64
|
|
0xb8588c, // 66
|
|
0xcc70a0, // 68
|
|
0xdc84b4, // 6A
|
|
0xec9cc4, // 6C
|
|
0xfcb0d4, // 6E
|
|
0x840014, // 70
|
|
0x982030, // 72
|
|
0xac3c4c, // 74
|
|
0xc05868, // 76
|
|
0xd0707c, // 78
|
|
0xe08894, // 7A
|
|
0xeca0a8, // 7C
|
|
0xfcb4bc, // 7E
|
|
0x880000, // 80
|
|
0x9c201c, // 82
|
|
0xb04038, // 84
|
|
0xc05c50, // 86
|
|
0xd07468, // 88
|
|
0xe08c7c, // 8A
|
|
0xeca490, // 8C
|
|
0xfcb8a4, // 8E
|
|
0x7c1800, // 90
|
|
0x90381c, // 92
|
|
0xa85438, // 94
|
|
0xbc7050, // 96
|
|
0xcc8868, // 98
|
|
0xdc9c7c, // 9A
|
|
0xecb490, // 9C
|
|
0xfcc8a4, // 9E
|
|
0x5c2c00, // A0
|
|
0x784c1c, // A2
|
|
0x906838, // A4
|
|
0xac8450, // A6
|
|
0xc09c68, // A8
|
|
0xd4b47c, // AA
|
|
0xe8cc90, // AC
|
|
0xfce0a4, // AE
|
|
0x2c3c00, // B0
|
|
0x485c1c, // B2
|
|
0x647c38, // B4
|
|
0x809c50, // B6
|
|
0x94b468, // B8
|
|
0xacd07c, // BA
|
|
0xc0e490, // BC
|
|
0xd4fca4, // BE
|
|
0x003c00, // C0
|
|
0x205c20, // C2
|
|
0x407c40, // C4
|
|
0x5c9c5c, // C6
|
|
0x74b474, // C8
|
|
0x8cd08c, // CA
|
|
0xa4e4a4, // CC
|
|
0xb8fcb8, // CE
|
|
0x003814, // D0
|
|
0x1c5c34, // D2
|
|
0x387c50, // D4
|
|
0x50986c, // D6
|
|
0x68b484, // D8
|
|
0x7ccc9c, // DA
|
|
0x90e4b4, // DC
|
|
0xa4fcc8, // DE
|
|
0x00302c, // E0
|
|
0x1c504c, // E2
|
|
0x347068, // E4
|
|
0x4c8c84, // E6
|
|
0x64a89c, // E8
|
|
0x78c0b4, // EA
|
|
0x88d4cc, // EC
|
|
0x9cece0, // EE
|
|
0x002844, // F0
|
|
0x184864, // F2
|
|
0x306884, // F4
|
|
0x4484a0, // F6
|
|
0x589cb8, // F8
|
|
0x6cb4d0, // FA
|
|
0x7ccce8, // FC
|
|
0x8ce0fc // FE
|
|
];
|
|
|
|
var COLORS_RGBA = new Uint32Array(256);
|
|
var COLORS_WEB = [];
|
|
for (var i = 0; i < 256; i++) {
|
|
COLORS_RGBA[i] = ATARI_NTSC_RGB[i >> 1] | 0xff000000;
|
|
COLORS_WEB[i] = "#" + hex(rgb2bgr(ATARI_NTSC_RGB[i >> 1]), 6);
|
|
}
|
|
|