8bitworkshop/src/machine/chips/gtia.ts

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);
}