1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-11-29 14:51:17 +00:00

fixed VDP

This commit is contained in:
Steven Hugg 2018-11-30 22:01:36 -05:00
parent 8a1c88f3de
commit 385cb7acc2
3 changed files with 287 additions and 76 deletions

View File

@ -137,7 +137,7 @@ const _ColecoVisionPlatform = function(mainElement) {
} }
} }
}; };
vdp = new TMS9918A(video.canvas, cru, true); // true = 4 sprites/line vdp = new TMS9918A(video.getFrameData(), cru, true); // true = 4 sprites/line
setKeyboardFromMap(video, inputs, COLECOVISION_KEYCODE_MAP); setKeyboardFromMap(video, inputs, COLECOVISION_KEYCODE_MAP);
timer = new AnimationTimer(60, this.nextFrame.bind(this)); timer = new AnimationTimer(60, this.nextFrame.bind(this));
} }
@ -149,10 +149,9 @@ const _ColecoVisionPlatform = function(mainElement) {
advance(novideo : boolean) { advance(novideo : boolean) {
for (var sl=0; sl<numTotalScanlines; sl++) { for (var sl=0; sl<numTotalScanlines; sl++) {
this.runCPU(cpu, cpuCyclesPerLine); this.runCPU(cpu, cpuCyclesPerLine);
if (sl < numVisibleScanlines)
vdp.drawScanline(sl); vdp.drawScanline(sl);
} }
vdp.updateCanvas(); video.updateFrame();
} }
loadROM(title, data) { loadROM(title, data) {

View File

@ -121,7 +121,7 @@ const _SG1000Platform = function(mainElement, isSMS:boolean) {
} }
}; };
var vdpclass = isSMS ? SMSVDP : TMS9918A; var vdpclass = isSMS ? SMSVDP : TMS9918A;
vdp = new vdpclass(video.canvas, cru, true); // true = 4 sprites/line vdp = new vdpclass(video.getFrameData(), cru, true); // true = 4 sprites/line
setKeyboardFromMap(video, inputs, SG1000_KEYCODE_MAP); setKeyboardFromMap(video, inputs, SG1000_KEYCODE_MAP);
timer = new AnimationTimer(60, this.nextFrame.bind(this)); timer = new AnimationTimer(60, this.nextFrame.bind(this));
} }
@ -134,10 +134,9 @@ const _SG1000Platform = function(mainElement, isSMS:boolean) {
for (var sl=0; sl<numTotalScanlines; sl++) { for (var sl=0; sl<numTotalScanlines; sl++) {
this.currentScanline = sl; this.currentScanline = sl;
this.runCPU(cpu, cpuCyclesPerLine); this.runCPU(cpu, cpuCyclesPerLine);
if (sl < numVisibleScanlines)
vdp.drawScanline(sl); vdp.drawScanline(sl);
} }
vdp.updateCanvas(); video.updateFrame();
} }
loadROM(title, data) { loadROM(title, data) {

View File

@ -17,18 +17,17 @@ enum TMS9918A_Mode {
TEXT = 1, TEXT = 1,
BITMAP = 2, BITMAP = 2,
MULTICOLOR = 3, MULTICOLOR = 3,
BITMAP_TEXT = 4, MODE4 = 4,
BITMAP_MULTICOLOR = 5, BITMAP_TEXT = 5,
ILLEGAL = 6, BITMAP_MULTICOLOR = 6,
MODE4 = 7, ILLEGAL = 7,
}; };
/** interface CPUInterface {
* @constructor setVDPInterrupt(b:boolean);
*/ }
export class TMS9918A {
canvas; export class TMS9918A {
cru : { setVDPInterrupt: (b:boolean) => void }; cru : { setVDPInterrupt: (b:boolean) => void };
enableFlicker : boolean; enableFlicker : boolean;
@ -63,16 +62,14 @@ export class TMS9918A {
flicker : boolean; flicker : boolean;
redrawRequired : boolean; redrawRequired : boolean;
canvasContext; fb32 : Uint32Array;
imageData;
datau32;
width : number; width : number;
height : number; height : number;
constructor(canvas, cru, enableFlicker:boolean) { constructor(fb32:Uint32Array, cru:CPUInterface, enableFlicker:boolean) {
this.canvas = canvas; this.fb32 = fb32;
this.cru = cru; this.cru = cru;
this.enableFlicker = enableFlicker; this.enableFlicker = enableFlicker;
@ -95,7 +92,6 @@ export class TMS9918A {
RGBA(255, 255, 255) RGBA(255, 255, 255)
]; ];
this.canvasContext = this.canvas.getContext("2d");
this.reset(); this.reset();
} }
@ -133,33 +129,12 @@ export class TMS9918A {
this.flicker = this.enableFlicker; this.flicker = this.enableFlicker;
this.redrawRequired = true; this.redrawRequired = true;
this.canvas.width = 304; this.width = 304;
this.canvas.height = 240; this.height = 240;
this.canvasContext.fillStyle = '#'+hex(this.palette[7],6);
this.canvasContext.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Build the array containing the canvas bitmap (256 * 192 * 4 bytes (r,g,b,a) format each pixel)
this.imageData = this.canvasContext.getImageData(0, 0, this.canvas.width, this.canvas.height);
this.datau32 = new Uint32Array(this.imageData.data.buffer);
this.width = this.canvas.width;
this.height = this.canvas.height;
}
drawFrame(timestamp:number) {
if (this.redrawRequired) {
for (var y = 0; y < this.height; y++) {
this.drawScanline(y);
}
this.updateCanvas();
this.redrawRequired = false;
}
}
initFrame(timestamp:number) {
} }
drawScanline(y:number) { drawScanline(y:number) {
var imageData = this.datau32, var imageData = this.fb32,
width = this.width, width = this.width,
imageDataAddr = (y * width), imageDataAddr = (y * width),
screenMode = this.screenMode, screenMode = this.screenMode,
@ -350,10 +325,6 @@ export class TMS9918A {
} }
} }
updateCanvas() {
this.canvasContext.putImageData(this.imageData, 0, 0);
}
setReadAddress(i:number) { setReadAddress(i:number) {
this.addressRegister = ((i & 0x3f) << 8) | (this.addressRegister & 0x00FF); this.addressRegister = ((i & 0x3f) << 8) | (this.addressRegister & 0x00FF);
this.prefetchByte = this.ram[this.addressRegister++]; this.prefetchByte = this.ram[this.addressRegister++];
@ -533,7 +504,7 @@ export class TMS9918A {
readStatus() : number { readStatus() : number {
var i = this.statusRegister; var i = this.statusRegister;
this.statusRegister = 0x1F; this.statusRegister = 0x1F; // TODO: &= 0x1f?
if (this.interruptsOn) { if (this.interruptsOn) {
this.cru.setVDPInterrupt(false); this.cru.setVDPInterrupt(false);
} }
@ -572,7 +543,16 @@ export class TMS9918A {
return 0x800; return 0x800;
} }
} }
getDebugTables() {
var tables : [string,number,number][] = [
["Pattern Table", this.charPatternTable, this.patternTableSize()],
["Name Table", this.nameTable, 0x300],
["Color Table", this.colorTable, this.colorTableSize()],
["Sprite Patterns", this.spritePatternTable, 64*32],
["Sprite Attributes", this.spriteAttributeTable, 4*32],
];
return tables;
}
getRegsString() : string { getRegsString() : string {
const w = 20; const w = 20;
var s = "Registers:"; var s = "Registers:";
@ -580,13 +560,7 @@ export class TMS9918A {
s += " " + hex(this.registers[i],2); s += " " + hex(this.registers[i],2);
} }
s += "\n\n"; s += "\n\n";
var tables : [string,number,number][] = [ var tables = this.getDebugTables();
["Pattern Table", this.charPatternTable, this.patternTableSize()],
["Image Table", this.nameTable, 0x300],
["Color Table", this.colorTable, this.colorTableSize()],
["Sprite Patterns", this.spritePatternTable, 64*32],
["Sprite Attributes", this.spriteAttributeTable, 4*32],
];
for (var row of tables) { for (var row of tables) {
if (row[2] > 0) if (row[2] > 0)
s += lpad(row[0], w) + ": $" + hex(row[1],4) + " - $" + hex(row[1]+row[2]-1,4) + "\n"; s += lpad(row[0], w) + ": $" + hex(row[1],4) + " - $" + hex(row[1]+row[2]-1,4) + "\n";
@ -701,14 +675,15 @@ export class SMSVDP extends TMS9918A {
writeToCRAM : boolean; writeToCRAM : boolean;
cram = new Uint8Array(32); // color RAM cram = new Uint8Array(32); // color RAM
cpalette = new Uint32Array(32); // color RAM (RGBA) cpalette = new Uint32Array(32); // color RAM (RGBA)
registers = new Uint8Array(16); // 8 more registers registers = new Uint8Array(16); // 8 more registers (actually only 5)
vramUntwiddled = new Uint8Array(0x8000);
spriteBufferBackground = new Uint8Array(256);
updateMode(reg0:number, reg1:number) { updateMode(reg0:number, reg1:number) {
if (reg0 & 0x04) { if (reg0 & 0x04) {
this.screenMode = TMS9918A_Mode.MODE4; this.screenMode = TMS9918A_Mode.MODE4;
this.nameTable = (this.registers[2] & 0xf) << 10; this.nameTable = ((this.registers[2] & 0xf) << 10) & 0x3800;
this.spriteAttributeTable = (this.registers[5] & 0x7f) << 7; this.spriteAttributeTable = (this.registers[5] & 0x7e) << 7;
this.spritePatternTable = (this.registers[6] & 0x7) << 11;
} else { } else {
super.updateMode(reg0, reg1); super.updateMode(reg0, reg1);
} }
@ -724,6 +699,7 @@ export class SMSVDP extends TMS9918A {
setVDPWriteRegister(i:number) { setVDPWriteRegister(i:number) {
super.setVDPWriteRegister(i); super.setVDPWriteRegister(i);
this.writeToCRAM = false; this.writeToCRAM = false;
this.ramMask = 0x3fff;
} }
setVDPWriteCommand3(i:number) { setVDPWriteCommand3(i:number) {
this.writeToCRAM = true; this.writeToCRAM = true;
@ -736,7 +712,25 @@ export class SMSVDP extends TMS9918A {
this.addressRegister &= this.ramMask; this.addressRegister &= this.ramMask;
this.redrawRequired = true; this.redrawRequired = true;
} else { } else {
var oldAddress = this.addressRegister;
super.writeData(i); super.writeData(i);
this.writeTwiddled(oldAddress, i);
}
}
writeTwiddled(vdp_addr:number, val:number) {
var planarBase = vdp_addr & 0x3ffc;
var twiddledBase = planarBase * 2;
var val0 = this.ram[planarBase];
var val1 = this.ram[planarBase + 1];
var val2 = this.ram[planarBase + 2];
var val3 = this.ram[planarBase + 3];
for (var i = 0; i < 8; ++i) {
var effectiveBit = 7 - i;
var index = (((val0 >>> effectiveBit) & 1))
| (((val1 >>> effectiveBit) & 1) << 1)
| (((val2 >>> effectiveBit) & 1) << 2)
| (((val3 >>> effectiveBit) & 1) << 3);
this.vramUntwiddled[twiddledBase + i] = index;
} }
} }
getState() { getState() {
@ -750,12 +744,13 @@ export class SMSVDP extends TMS9918A {
} }
drawScanline(y:number) { drawScanline(y:number) {
if (this.screenMode == TMS9918A_Mode.MODE4) // TODO: check for other uses if (this.screenMode == TMS9918A_Mode.MODE4) // TODO: check for other uses
this.drawScanlineMode4(y); // special mode 4 //this.drawScanlineMode4(y);
this.rasterize_line(y); // special mode 4
else else
super.drawScanline(y); super.drawScanline(y);
} }
drawScanlineMode4(y:number) { drawScanlineMode4(y:number) {
var imageData = this.datau32, var imageData = this.fb32,
width = this.width, width = this.width,
imageDataAddr = (y * width), imageDataAddr = (y * width),
drawWidth = 256, drawWidth = 256,
@ -814,7 +809,6 @@ export class SMSVDP extends TMS9918A {
var sx = ram[spriteAttributeTable + s*2 + 0x80]; var sx = ram[spriteAttributeTable + s*2 + 0x80];
var sPatternNo = ram[spriteAttributeTable + s*2 + 0x81]; var sPatternNo = ram[spriteAttributeTable + s*2 + 0x81];
var sColor = 0; // TODO var sColor = 0; // TODO
//var sColor = ram[spriteAttributeAddr + 3] & 0x0F;
//if ((ram[spriteAttributeAddr + 3] & 0x80) !== 0) { //if ((ram[spriteAttributeAddr + 3] & 0x80) !== 0) {
// sx -= 32; // sx -= 32;
//} //}
@ -857,15 +851,7 @@ export class SMSVDP extends TMS9918A {
var nameOfs = nameTable + rowOffset + ((x1 >> 3) << 1); var nameOfs = nameTable + rowOffset + ((x1 >> 3) << 1);
name = ram[nameOfs] + (ram[nameOfs+1] << 8); name = ram[nameOfs] + (ram[nameOfs+1] << 8);
var patofs = ((((name & 0x1ff) << 3) + lineOffset) << 2); var patofs = ((((name & 0x1ff) << 3) + lineOffset) << 2);
var pat0 = ram[patofs+0]; color = this.vramUntwiddled[patofs*2 + (x1&7)];
var pat1 = ram[patofs+1];
var pat2 = ram[patofs+2];
var pat3 = ram[patofs+3];
pat0 >>= x1 & 7;
pat1 >>= x1 & 7;
pat2 >>= x1 & 7;
pat3 >>= x1 & 7;
color = (pat0&1) | ((pat1&1)<<1) | ((pat2&1)<<2) | ((pat3&1)<<3);
if (color === 0) { if (color === 0) {
color = bgColor; color = bgColor;
} }
@ -908,5 +894,232 @@ export class SMSVDP extends TMS9918A {
} }
} }
findSprites(line:number) {
var spriteInfo = this.spriteAttributeTable;
var active = [];
var spriteHeight = 8;
var i;
if (this.registers[1] & 2) {
spriteHeight = 16;
}
for (i = 0; i < 64; i++) {
var y = this.ram[spriteInfo + i];
if (y === 208) {
break;
}
if (y >= 240) y -= 256;
if (line >= y && line < (y + spriteHeight)) {
if (active.length === 8) {
this.statusRegister |= 0x40; // Sprite overflow
break;
}
active.push([
this.ram[spriteInfo + 128 + i * 2],
this.ram[spriteInfo + 128 + i * 2 + 1],
y]);
}
}
return active;
}
rasterize_background(lineAddr:number, pixelOffset:number, tileData:number, tileDef:number, transparent:boolean) {
lineAddr = lineAddr | 0;
pixelOffset = pixelOffset | 0;
tileData = tileData | 0;
tileDef = (tileDef | 0) * 2;
var i, tileDefInc;
if ((tileData & (1 << 9))) {
tileDefInc = -1;
tileDef += 7;
} else {
tileDefInc = 1;
}
const paletteOffset = (tileData & (1 << 11)) ? 16 : 0;
var index;
if (transparent && paletteOffset === 0) {
for (i = 0; i < 8; i++) {
index = this.vramUntwiddled[tileDef];
tileDef += tileDefInc;
if (index !== 0) this.fb32[lineAddr + pixelOffset] = this.cpalette[index];
pixelOffset = (pixelOffset + 1) & 255;
}
} else {
for (i = 0; i < 8; i++) {
index = this.vramUntwiddled[tileDef] + paletteOffset;
tileDef += tileDefInc;
this.fb32[lineAddr + pixelOffset] = this.cpalette[index];
pixelOffset = (pixelOffset + 1) & 255;
}
}
}
clear_background(lineAddr:number, pixelOffset:number) {
lineAddr = lineAddr | 0;
pixelOffset = pixelOffset | 0;
var i;
const rgb = this.cpalette[0];
for (i = 0; i < 8; ++i) {
this.fb32[lineAddr + pixelOffset] = rgb;
pixelOffset = (pixelOffset + 1) & 255;
}
}
rasterize_background_line(lineAddr:number, pixelOffset:number, nameAddr:number, yMod:number) {
lineAddr = lineAddr | 0;
pixelOffset = pixelOffset | 0;
nameAddr = nameAddr | 0;
const yOffset = (yMod | 0) * 4;
for (var i = 0; i < 32; i++) {
// TODO: static left-hand rows.
var tileData = this.ram[nameAddr + i * 2] | (this.ram[nameAddr + i * 2 + 1] << 8);
var tileNum = tileData & 511;
var tileDef = 32 * tileNum;
if (tileData & (1 << 10)) {
tileDef += 28 - yOffset;
} else {
tileDef += yOffset;
}
if ((tileData & (1 << 12)) === 0) {
this.rasterize_background(lineAddr, pixelOffset, tileData, tileDef, false);
} else {
this.clear_background(lineAddr, pixelOffset);
}
pixelOffset = (pixelOffset + 8) & 255;
}
}
rasterize_foreground_line(lineAddr:number, pixelOffset:number, nameAddr:number, yMod:number) {
lineAddr = lineAddr | 0;
pixelOffset = pixelOffset | 0;
nameAddr = nameAddr | 0;
const yOffset = (yMod | 0) * 4;
for (var i = 0; i < 32; i++) {
// TODO: static left-hand rows.
var tileData = this.ram[nameAddr + i * 2] | (this.ram[nameAddr + i * 2 + 1] << 8);
if ((tileData & (1 << 12)) === 0) continue;
var tileNum = tileData & 511;
var tileDef = 32 * tileNum;
if (tileData & (1 << 10)) {
tileDef += 28 - yOffset;
} else {
tileDef += yOffset;
}
this.rasterize_background(lineAddr, ((i * 8) + pixelOffset & 0xff), tileData, tileDef, true);
}
}
rasterize_sprites(line:number, lineAddr:number, pixelOffset:number, sprites) {
lineAddr = lineAddr | 0;
pixelOffset = pixelOffset | 0;
const spriteBase = (this.registers[6] & 4) ? 0x2000 : 0;
// TODO: sprite X-8 shift
// TODO: sprite double size
for (var i = 0; i < 256; ++i) {
var xPos = i;//(i + this.registers[8]) & 0xff;
var spriteFoundThisX = false;
var writtenTo = false;
var minDistToNext = 256;
for (var k = 0; k < sprites.length; k++) {
var sprite = sprites[k];
var offset = xPos - sprite[0];
// Sprite to the right of the current X?
if (offset < 0) {
// Find out how far it would be to skip to this sprite
var distToSprite = -offset;
// Keep the minimum distance to the next sprite to the right.
if (distToSprite < minDistToNext) minDistToNext = distToSprite;
continue;
}
if (offset >= 8) continue;
spriteFoundThisX = true;
var spriteLine = line - sprite[2];
var spriteAddr = spriteBase + sprite[1] * 32 + spriteLine * 4;
var untwiddledAddr = spriteAddr * 2 + offset;
var index = this.vramUntwiddled[untwiddledAddr];
if (index === 0) {
continue;
}
if (writtenTo) {
// We have a collision!.
this.statusRegister |= 0x20;
break;
}
this.fb32[lineAddr + ((pixelOffset + i - this.registers[8]) & 0xff)] = this.cpalette[16 + index];
writtenTo = true;
}
if (!spriteFoundThisX && minDistToNext > 1) {
// If we didn't find a sprite on this X, then we can skip ahead by the minimum
// dist to next (minus one to account for loop add)
i += minDistToNext - 1;
}
}
}
border_clear(lineAddr:number, count:number) {
lineAddr = lineAddr | 0;
count = count | 0;
const borderIndex = 16 + (this.registers[7] & 0xf);
const borderRGB = this.cpalette[borderIndex];
for (var i = 0; i < count; i++)
this.fb32[lineAddr + i] = borderRGB;
}
rasterize_line(line:number) {
var vdp_regs = this.registers;
var drawHeight = 192; // TODO?
var hBorder = (this.width - 256) >> 1;
var vBorder = (this.height - 192) >> 1; // TODO?
line = (line - vBorder) | 0; // TODO?
const lineAddr = (line * this.width + hBorder) | 0;
if (!(line >= 0 && line < 224 && this.displayOn)) {
this.border_clear(lineAddr, this.width);
return;
}
if ((vdp_regs[1] & 64) === 0) {
this.border_clear(lineAddr, this.width);
return;
}
var effectiveLine = line + vdp_regs[9];
if (effectiveLine >= 224) {
effectiveLine -= 224;
}
const sprites = this.findSprites(line);
const pixelOffset = ((vdp_regs[0] & 64) && line < 16) ? 0 : vdp_regs[8];
const nameAddr = this.nameTable + (effectiveLine >>> 3) * 64;
const yMod = effectiveLine & 7;
this.rasterize_background_line(lineAddr, pixelOffset, nameAddr, yMod);
this.rasterize_sprites(line, lineAddr, pixelOffset, sprites);
this.rasterize_foreground_line(lineAddr, pixelOffset, nameAddr, yMod);
if (vdp_regs[0] & (1 << 5)) {
// Blank out left hand column.
this.border_clear(lineAddr, 8);
}
if (line == drawHeight) {
this.statusRegister |= 0x80;
if (this.interruptsOn) {
this.cru.setVDPInterrupt(true);
}
}
}
getDebugTables() {
if (this.screenMode == TMS9918A_Mode.MODE4) {
var tables : [string,number,number][] = [
["Pattern Table", 0, 512*32],
["Name Table", this.nameTable, 32*32*2], // TODO: size
["Sprite Attributes", this.spriteAttributeTable, 256],
]
return tables;
} else {
return super.getDebugTables();
}
}
}; };