BizHawk/BizHawk.Emulation/Computers/Commodore64/VicII.cs

1738 lines
42 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BizHawk.Emulation.Computers.Commodore64
{
public class SpriteRegs
{
public int MC; // (internal)
public int MCBASE; // (internal)
public bool MD; // (internal)
public bool MDMA; // (internal)
public int MPTR; // (internal)
public Int32 MSR; // (internal)
public bool MSRA; // (internal)
public int MSRC; // (internal)
public int MxC; // sprite color
public bool MxD; // sprite-data collision
public bool MxDP; // sprite priority
public bool MxE; // sprite enabled
public bool MxM; // sprite-sprite collision
public bool MxMC; // sprite multicolor
public int MxX; // sprite X coordinate
public bool MxXE; // sprite X expansion
public bool MxXEToggle; // (internal)
public int MxXLatch; // (internal)
public int MxY; // sprite Y coordinate
public bool MxYE; // sprite Y expansion
public bool MxYEToggle; // (internal)
}
public class VicIIRegs
{
public bool BMM; // bitmap mode
public int[] BxC = new int[4]; // background colors
public int CB; // character bitmap offset
public bool CSEL; // column select
public bool DEN; // display enabled
public int EC; // border color
public bool ECM; // extra color mode
public bool ELP; // enable lightpen interrupt
public bool EMBC; // enable sprite-data interrupt
public bool EMMC; // enable sprite-sprite interrupt
public bool ERST; // enable raster line interrupt
public bool ILP; // light pen interrupt active
public bool IMBC; // sprite-data interrupt active
public bool IMMC; // sprite-sprite interrupt active
public bool IRQ; // interrupt was triggered
public bool IRST; // raster line interrupt active
public int LPX; // lightpen X coordinate
public int LPY; // lightpen Y coordinate
public bool MCM; // multicolor mode
public int[] MMx = new int[2]; // sprite extra color
public int RASTER; // current raster line
public int RC; // (internal)
public bool RES; // reset bit (does nothing in this version of the VIC)
public bool RSEL; // row select
public int VC; // (internal)
public int VCBASE; // (internal)
public int VM; // video memory offset
public int VMLI; // (internal)
public int XSCROLL; // X scroll position
public int YSCROLL; // Y scroll position
public SpriteRegs[] Sprites = new SpriteRegs[8];
public VicIIRegs()
{
// power on state
this[0x16] = 0xC0;
this[0x18] = 0x01;
this[0x19] = 0x71;
this[0x1A] = 0xF0;
// init sprites
for (int i = 0; i < 8; i++)
Sprites[i] = new SpriteRegs();
}
public byte this[int addr]
{
get
{
int result = 0xFF; // value for any open bits
addr &= 0x3F;
switch (addr)
{
case 0x00:
case 0x02:
case 0x04:
case 0x06:
case 0x08:
case 0x0A:
case 0x0C:
case 0x0E:
result = Sprites[addr >> 1].MxX;
break;
case 0x01:
case 0x03:
case 0x05:
case 0x07:
case 0x09:
case 0x0B:
case 0x0D:
case 0x0F:
result = Sprites[addr >> 1].MxY;
break;
case 0x10:
result = ((Sprites[0].MxX & 0x100) != 0) ? 0x01 : 0x00;
result |= ((Sprites[1].MxX & 0x100) != 0) ? 0x02 : 0x00;
result |= ((Sprites[2].MxX & 0x100) != 0) ? 0x04 : 0x00;
result |= ((Sprites[3].MxX & 0x100) != 0) ? 0x08 : 0x00;
result |= ((Sprites[4].MxX & 0x100) != 0) ? 0x10 : 0x00;
result |= ((Sprites[5].MxX & 0x100) != 0) ? 0x20 : 0x00;
result |= ((Sprites[6].MxX & 0x100) != 0) ? 0x40 : 0x00;
result |= ((Sprites[7].MxX & 0x100) != 0) ? 0x80 : 0x00;
break;
case 0x11:
result = YSCROLL & 0x07;
result |= (RSEL ? 0x08 : 0x00);
result |= (DEN ? 0x10 : 0x00);
result |= (BMM ? 0x20 : 0x00);
result |= (ECM ? 0x40 : 0x00);
result |= ((RASTER & 0x100) >> 1);
break;
case 0x12:
result = RASTER & 0xFF;
break;
case 0x13:
result = LPX;
break;
case 0x14:
result = LPY;
break;
case 0x15:
result = (Sprites[0].MxE ? 0x01 : 0x00);
result |= (Sprites[1].MxE ? 0x02 : 0x00);
result |= (Sprites[2].MxE ? 0x04 : 0x00);
result |= (Sprites[3].MxE ? 0x08 : 0x00);
result |= (Sprites[4].MxE ? 0x10 : 0x00);
result |= (Sprites[5].MxE ? 0x20 : 0x00);
result |= (Sprites[6].MxE ? 0x40 : 0x00);
result |= (Sprites[7].MxE ? 0x80 : 0x00);
break;
case 0x16:
result &= 0xC0;
result |= XSCROLL & 0x07;
result |= (CSEL ? 0x08 : 0x00);
result |= (MCM ? 0x10 : 0x00);
result |= (RES ? 0x20 : 0x00);
break;
case 0x17:
result = (Sprites[0].MxYE ? 0x01 : 0x00);
result |= (Sprites[1].MxYE ? 0x02 : 0x00);
result |= (Sprites[2].MxYE ? 0x04 : 0x00);
result |= (Sprites[3].MxYE ? 0x08 : 0x00);
result |= (Sprites[4].MxYE ? 0x10 : 0x00);
result |= (Sprites[5].MxYE ? 0x20 : 0x00);
result |= (Sprites[6].MxYE ? 0x40 : 0x00);
result |= (Sprites[7].MxYE ? 0x80 : 0x00);
break;
case 0x18:
result &= 0x01;
result |= (CB & 0x07) << 1;
result |= (VM & 0x0F) << 4;
break;
case 0x19:
result &= 0x70;
result |= (IRST ? 0x01 : 0x00);
result |= (IMBC ? 0x02 : 0x00);
result |= (IMMC ? 0x04 : 0x00);
result |= (ILP ? 0x08 : 0x00);
result |= (IRQ ? 0x80 : 0x00);
break;
case 0x1A:
result &= 0xF0;
result |= (ERST ? 0x01 : 0x00);
result |= (EMBC ? 0x02 : 0x00);
result |= (EMMC ? 0x04 : 0x00);
result |= (ELP ? 0x08 : 0x00);
break;
case 0x1B:
result = (Sprites[0].MxDP ? 0x01 : 0x00);
result |= (Sprites[1].MxDP ? 0x02 : 0x00);
result |= (Sprites[2].MxDP ? 0x04 : 0x00);
result |= (Sprites[3].MxDP ? 0x08 : 0x00);
result |= (Sprites[4].MxDP ? 0x10 : 0x00);
result |= (Sprites[5].MxDP ? 0x20 : 0x00);
result |= (Sprites[6].MxDP ? 0x40 : 0x00);
result |= (Sprites[7].MxDP ? 0x80 : 0x00);
break;
case 0x1C:
result = (Sprites[0].MxMC ? 0x01 : 0x00);
result |= (Sprites[1].MxMC ? 0x02 : 0x00);
result |= (Sprites[2].MxMC ? 0x04 : 0x00);
result |= (Sprites[3].MxMC ? 0x08 : 0x00);
result |= (Sprites[4].MxMC ? 0x10 : 0x00);
result |= (Sprites[5].MxMC ? 0x20 : 0x00);
result |= (Sprites[6].MxMC ? 0x40 : 0x00);
result |= (Sprites[7].MxMC ? 0x80 : 0x00);
break;
case 0x1D:
result = (Sprites[0].MxXE ? 0x01 : 0x00);
result |= (Sprites[1].MxXE ? 0x02 : 0x00);
result |= (Sprites[2].MxXE ? 0x04 : 0x00);
result |= (Sprites[3].MxXE ? 0x08 : 0x00);
result |= (Sprites[4].MxXE ? 0x10 : 0x00);
result |= (Sprites[5].MxXE ? 0x20 : 0x00);
result |= (Sprites[6].MxXE ? 0x40 : 0x00);
result |= (Sprites[7].MxXE ? 0x80 : 0x00);
break;
case 0x1E:
result = (Sprites[0].MxM ? 0x01 : 0x00);
result |= (Sprites[1].MxM ? 0x02 : 0x00);
result |= (Sprites[2].MxM ? 0x04 : 0x00);
result |= (Sprites[3].MxM ? 0x08 : 0x00);
result |= (Sprites[4].MxM ? 0x10 : 0x00);
result |= (Sprites[5].MxM ? 0x20 : 0x00);
result |= (Sprites[6].MxM ? 0x40 : 0x00);
result |= (Sprites[7].MxM ? 0x80 : 0x00);
break;
case 0x1F:
result = (Sprites[0].MxD ? 0x01 : 0x00);
result |= (Sprites[1].MxD ? 0x02 : 0x00);
result |= (Sprites[2].MxD ? 0x04 : 0x00);
result |= (Sprites[3].MxD ? 0x08 : 0x00);
result |= (Sprites[4].MxD ? 0x10 : 0x00);
result |= (Sprites[5].MxD ? 0x20 : 0x00);
result |= (Sprites[6].MxD ? 0x40 : 0x00);
result |= (Sprites[7].MxD ? 0x80 : 0x00);
break;
case 0x20:
result &= 0xF0;
result |= EC & 0x0F;
break;
case 0x21:
case 0x22:
case 0x23:
case 0x24:
result &= 0xF0;
result |= BxC[addr - 0x21] & 0x0F;
break;
case 0x25:
case 0x26:
result &= 0xF0;
result |= MMx[addr - 0x25] & 0x0F;
break;
case 0x27:
case 0x28:
case 0x29:
case 0x2A:
case 0x2B:
case 0x2C:
case 0x2D:
case 0x2E:
result &= 0xF0;
result |= Sprites[addr - 0x27].MxC & 0x0F;
break;
default:
result = 0xFF;
break;
}
return (byte)(result);
}
set
{
int index;
int val = value;
addr &= 0x3F;
switch (addr)
{
case 0x00:
case 0x02:
case 0x04:
case 0x06:
case 0x08:
case 0x0A:
case 0x0C:
case 0x0E:
index = addr >> 1;
Sprites[index].MxX &= 0x100;
Sprites[index].MxX |= (val & 0xFF);
break;
case 0x01:
case 0x03:
case 0x05:
case 0x07:
case 0x09:
case 0x0B:
case 0x0D:
case 0x0F:
index = addr >> 1;
Sprites[index].MxY &= 0x100;
Sprites[index].MxY |= (val & 0xFF);
break;
case 0x10:
Sprites[0].MxX = (Sprites[0].MxX & 0xFF) | ((val & 0x01) << 8);
Sprites[1].MxX = (Sprites[1].MxX & 0xFF) | ((val & 0x02) << 7);
Sprites[2].MxX = (Sprites[2].MxX & 0xFF) | ((val & 0x04) << 6);
Sprites[3].MxX = (Sprites[3].MxX & 0xFF) | ((val & 0x08) << 5);
Sprites[4].MxX = (Sprites[4].MxX & 0xFF) | ((val & 0x10) << 4);
Sprites[5].MxX = (Sprites[5].MxX & 0xFF) | ((val & 0x20) << 3);
Sprites[6].MxX = (Sprites[6].MxX & 0xFF) | ((val & 0x40) << 2);
Sprites[7].MxX = (Sprites[7].MxX & 0xFF) | ((val & 0x80) << 1);
break;
case 0x11:
YSCROLL = (val & 0x07);
RSEL = ((val & 0x08) != 0x00);
DEN = ((val & 0x10) != 0x00);
BMM = ((val & 0x20) != 0x00);
ECM = ((val & 0x40) != 0x00);
RASTER &= 0xFF;
RASTER |= ((val & 0x80) << 1);
break;
case 0x12:
RASTER &= 0x100;
RASTER |= (val & 0xFF);
break;
case 0x13:
LPX = (val & 0xFF);
break;
case 0x14:
LPY = (val & 0xFF);
break;
case 0x15:
Sprites[0].MxE = ((val & 0x01) != 0x00);
Sprites[1].MxE = ((val & 0x02) != 0x00);
Sprites[2].MxE = ((val & 0x04) != 0x00);
Sprites[3].MxE = ((val & 0x08) != 0x00);
Sprites[4].MxE = ((val & 0x10) != 0x00);
Sprites[5].MxE = ((val & 0x20) != 0x00);
Sprites[6].MxE = ((val & 0x40) != 0x00);
Sprites[7].MxE = ((val & 0x80) != 0x00);
break;
case 0x16:
XSCROLL = (val & 0x07);
CSEL = ((val & 0x08) != 0x00);
MCM = ((val & 0x10) != 0x00);
RES = ((val & 0x20) != 0x00);
break;
case 0x17:
Sprites[0].MxYE = ((val & 0x01) != 0x00);
Sprites[1].MxYE = ((val & 0x02) != 0x00);
Sprites[2].MxYE = ((val & 0x04) != 0x00);
Sprites[3].MxYE = ((val & 0x08) != 0x00);
Sprites[4].MxYE = ((val & 0x10) != 0x00);
Sprites[5].MxYE = ((val & 0x20) != 0x00);
Sprites[6].MxYE = ((val & 0x40) != 0x00);
Sprites[7].MxYE = ((val & 0x80) != 0x00);
break;
case 0x18:
CB = (val & 0x0E) >> 1;
VM = (val & 0xF0) >> 4;
break;
case 0x19:
IRST = ((val & 0x01) != 0x00);
IMBC = ((val & 0x02) != 0x00);
IMMC = ((val & 0x04) != 0x00);
ILP = ((val & 0x08) != 0x00);
break;
case 0x1A:
ERST = ((val & 0x01) != 0x00);
EMBC = ((val & 0x02) != 0x00);
EMMC = ((val & 0x04) != 0x00);
ELP = ((val & 0x08) != 0x00);
break;
case 0x1B:
Sprites[0].MxDP = ((val & 0x01) != 0x00);
Sprites[1].MxDP = ((val & 0x02) != 0x00);
Sprites[2].MxDP = ((val & 0x04) != 0x00);
Sprites[3].MxDP = ((val & 0x08) != 0x00);
Sprites[4].MxDP = ((val & 0x10) != 0x00);
Sprites[5].MxDP = ((val & 0x20) != 0x00);
Sprites[6].MxDP = ((val & 0x40) != 0x00);
Sprites[7].MxDP = ((val & 0x80) != 0x00);
break;
case 0x1C:
Sprites[0].MxMC = ((val & 0x01) != 0x00);
Sprites[1].MxMC = ((val & 0x02) != 0x00);
Sprites[2].MxMC = ((val & 0x04) != 0x00);
Sprites[3].MxMC = ((val & 0x08) != 0x00);
Sprites[4].MxMC = ((val & 0x10) != 0x00);
Sprites[5].MxMC = ((val & 0x20) != 0x00);
Sprites[6].MxMC = ((val & 0x40) != 0x00);
Sprites[7].MxMC = ((val & 0x80) != 0x00);
break;
case 0x1D:
Sprites[0].MxXE = ((val & 0x01) != 0x00);
Sprites[1].MxXE = ((val & 0x02) != 0x00);
Sprites[2].MxXE = ((val & 0x04) != 0x00);
Sprites[3].MxXE = ((val & 0x08) != 0x00);
Sprites[4].MxXE = ((val & 0x10) != 0x00);
Sprites[5].MxXE = ((val & 0x20) != 0x00);
Sprites[6].MxXE = ((val & 0x40) != 0x00);
Sprites[7].MxXE = ((val & 0x80) != 0x00);
break;
case 0x1E:
Sprites[0].MxM = ((val & 0x01) != 0x00);
Sprites[1].MxM = ((val & 0x02) != 0x00);
Sprites[2].MxM = ((val & 0x04) != 0x00);
Sprites[3].MxM = ((val & 0x08) != 0x00);
Sprites[4].MxM = ((val & 0x10) != 0x00);
Sprites[5].MxM = ((val & 0x20) != 0x00);
Sprites[6].MxM = ((val & 0x40) != 0x00);
Sprites[7].MxM = ((val & 0x80) != 0x00);
break;
case 0x1F:
Sprites[0].MxD = ((val & 0x01) != 0x00);
Sprites[1].MxD = ((val & 0x02) != 0x00);
Sprites[2].MxD = ((val & 0x04) != 0x00);
Sprites[3].MxD = ((val & 0x08) != 0x00);
Sprites[4].MxD = ((val & 0x10) != 0x00);
Sprites[5].MxD = ((val & 0x20) != 0x00);
Sprites[6].MxD = ((val & 0x40) != 0x00);
Sprites[7].MxD = ((val & 0x80) != 0x00);
break;
case 0x20:
EC = (val & 0x0F);
break;
case 0x21:
case 0x22:
case 0x23:
case 0x24:
BxC[addr - 0x21] = val & 0x0F;
break;
case 0x25:
case 0x26:
MMx[addr - 0x25] = val & 0x0F;
break;
case 0x27:
case 0x28:
case 0x29:
case 0x2A:
case 0x2B:
case 0x2C:
case 0x2D:
case 0x2E:
Sprites[addr - 0x27].MxC = val & 0x0F;
break;
}
}
}
}
public partial class VicII : IVideoProvider
{
// graphics buffer
public int[] buffer;
public int bufferSize;
// palette
public int[] palette =
{
Colors.ARGB(0x00, 0x00, 0x00),
Colors.ARGB(0xFF, 0xFF, 0xFF),
Colors.ARGB(0x68, 0x37, 0x2B),
Colors.ARGB(0x70, 0xA4, 0xB2),
Colors.ARGB(0x6F, 0x3D, 0x86),
Colors.ARGB(0x58, 0x8D, 0x43),
Colors.ARGB(0x35, 0x28, 0x79),
Colors.ARGB(0xB8, 0xC7, 0x6F),
Colors.ARGB(0x6F, 0x4F, 0x25),
Colors.ARGB(0x43, 0x39, 0x00),
Colors.ARGB(0x9A, 0x67, 0x59),
Colors.ARGB(0x44, 0x44, 0x44),
Colors.ARGB(0x6C, 0x6C, 0x6C),
Colors.ARGB(0x9A, 0xD2, 0x84),
Colors.ARGB(0x6C, 0x5E, 0xB5),
Colors.ARGB(0x95, 0x95, 0x95)
};
// raster
public bool badLine;
public byte bitmapData;
public byte bitmapDataMask;
public int borderBottom;
public int borderLeft;
public bool borderOnMain;
public bool borderOnVertical;
public int borderRight;
public int borderTop;
public int characterColumn;
public byte characterData;
public byte characterDataBus;
public byte[] characterMemory;
public bool charactersEnabled;
public byte colorData;
public byte colorDataBus;
public byte[] colorMemory;
public int cycle;
public int cycleLeft;
public int cyclesPerFrame;
public bool dataForeground;
public bool displayEnabled;
public bool hBlank;
public bool idle;
public int[] pixelBuffer;
public bool[] pixelBufferForeground;
public int pixelBufferIndex;
public int pixelBufferLength;
public int rasterInterruptLine;
public int rasterLineLeft;
public int rasterOffset;
public int rasterOffsetX;
public int rasterTotalLines;
public int rasterWidth;
public int refreshAddress;
public int renderOffset;
public bool spriteForeground;
public SpriteGenerator[] spriteGenerators;
public int totalCycles;
public bool vBlank;
public int visibleBottom;
public int visibleHeight;
public int visibleLeft;
public bool visibleRenderX;
public bool visibleRenderY;
public int visibleRight;
public int visibleTop;
public int visibleWidth;
public Memory mem;
public VicIIRegs regs;
public ChipSignals signal;
private Action FetchC;
private Action FetchG;
private Func<int> Plotter;
private Action PerformCycleFunction;
private SpriteRegs[] sprites;
public VicII(ChipSignals newSignal, Region newRegion)
{
signal = newSignal;
switch (newRegion)
{
case Region.NTSC:
totalCycles = 65;
rasterTotalLines = 263;
rasterLineLeft = 0x19C;
cycleLeft = 0;
visibleLeft = 0x008;
visibleRight = 0x168;
visibleTop = 0x023;
visibleBottom = 0x004;
visibleRenderX = false;
visibleRenderY = false;
visibleWidth = 352;
visibleHeight = 232;
renderOffset = 0;
pixelBufferLength = 12;
PerformCycleFunction = PerformCycleNTSC;
break;
case Region.PAL:
totalCycles = 63;
rasterTotalLines = 312;
rasterLineLeft = 0x194;
cycleLeft = 0;
visibleLeft = 0x008;
visibleRight = 0x168;
visibleTop = 0x023;
visibleBottom = 0x10B;
visibleRenderX = false;
visibleRenderY = false;
visibleWidth = 352;
visibleHeight = 232;
renderOffset = 0;
PerformCycleFunction = PerformCyclePAL;
pixelBufferLength = 4;
break;
default:
break;
}
HardReset();
}
// increment raster line
private void AdvanceRaster()
{
regs.RASTER++;
// if we reach the bottom, reset to the top
if (regs.RASTER == rasterTotalLines)
{
regs.RASTER = 0;
regs.VCBASE = 0;
displayEnabled = false;
}
// check to see if we are within viewing area Y
if (regs.RASTER == visibleBottom)
{
visibleRenderY = false;
renderOffset = 0;
}
if (regs.RASTER == visibleTop)
visibleRenderY = true;
}
// standard text mode
public void Fetch000C()
{
int cAddress = (regs.VM << 10) | regs.VC;
characterDataBus = mem.VicRead((ushort)cAddress);
colorDataBus = mem.colorRam[regs.VC];
}
public void Fetch000G()
{
int gAddress = (regs.CB << 11) | (characterData << 3) | regs.RC;
bitmapData = mem.VicRead((ushort)gAddress);
}
// standard bitmap mode
public void Fetch010G()
{
int gAddress = ((regs.CB & 0x4) << 11) | (regs.VC << 3) | regs.RC;
bitmapData = mem.VicRead((ushort)gAddress);
}
// extra color text mode
public void Fetch100G()
{
int gAddress = (regs.CB << 11) | ((characterData & 0x3F) << 3) | regs.RC;
bitmapData = mem.VicRead((ushort)gAddress);
}
// invalid bitmap mode 110
public void Fetch110G()
{
int gAddress = ((regs.CB & 0x4) << 11) | ((regs.VC & 0x33F) << 3) | regs.RC;
bitmapData = mem.VicRead((ushort)gAddress);
}
// idle fetch
public void FetchIdleC()
{
characterDataBus = 0;
colorDataBus = 0;
}
public void FetchIdleG()
{
if (regs.ECM)
mem.VicRead(0x39FF);
else
mem.VicRead(0x3FFF);
}
public void HardReset()
{
// initialize raster
rasterWidth = totalCycles * 8;
rasterOffsetX = rasterLineLeft;
borderOnMain = true;
borderOnVertical = true;
// initialize buffer
buffer = new int[visibleWidth * visibleHeight];
bufferSize = buffer.Length;
// initialize screen buffer
characterMemory = new byte[40];
colorMemory = new byte[40];
pixelBuffer = new int[pixelBufferLength];
pixelBufferForeground = new bool[pixelBufferLength];
pixelBufferIndex = 0;
// initialize registers
idle = true;
refreshAddress = 0x3FFF;
regs = new VicIIRegs();
sprites = regs.Sprites;
regs.RC = 7;
signal.VicAEC = true;
signal.VicIRQ = false;
// initialize screen
UpdateBorder();
UpdatePlotter();
// some helpful values
cyclesPerFrame = totalCycles * rasterTotalLines;
// initialize sprite generators
spriteGenerators = new SpriteGenerator[8];
for (int i = 0; i < 8; i++)
spriteGenerators[i] = new SpriteGenerator(regs, i, rasterWidth, rasterLineLeft);
}
public byte Peek(int addr)
{
return regs[addr & 0x3F];
}
private void PerformBorderCheck()
{
if ((regs.RASTER == borderTop) && (regs.DEN))
borderOnVertical = false;
if (regs.RASTER == borderBottom)
borderOnVertical = true;
}
public void PerformCycle()
{
if (cycle >= totalCycles)
{
cycle = 0;
AdvanceRaster();
}
PerformCycleCommon();
PerformCycleFunction();
RenderCycle();
// increment cycle
cycle++;
UpdateInterrupts();
signal.VicIRQ = regs.IRQ;
}
// these operations are done every cycle
private void PerformCycleCommon()
{
// display enable check on line 030 (this must be run every cycle)
if (regs.RASTER == 0x030)
displayEnabled = (displayEnabled | regs.DEN);
// badline check
if (regs.RASTER >= 0x030 && regs.RASTER < 0x0F8)
badLine = ((regs.YSCROLL == (regs.RASTER & 0x07)) && displayEnabled);
else
badLine = false;
if (badLine)
idle = false;
// sprite Y stretch flipflop
for (int i = 0; i < 8; i++)
if (!sprites[i].MxYE)
sprites[i].MxYEToggle = true;
}
// operations timed to NTSC
private void PerformCycleNTSC()
{
switch (cycle)
{
case 0:
// rasterline IRQ happens on cycle 1 on rasterline 0
if (regs.RASTER > 0 && regs.RASTER == rasterInterruptLine)
regs.IRST = true;
PerformSpritePointerFetch(3);
break;
case 1:
// rasterline IRQ happens on cycle 1 on rasterline 0
if (regs.RASTER == 0 && regs.RASTER == rasterInterruptLine)
regs.IRST = true;
PerformSpriteDataFetch(3);
break;
case 2:
PerformSpritePointerFetch(4);
break;
case 3:
PerformSpriteDataFetch(4);
break;
case 4:
PerformSpritePointerFetch(5);
break;
case 5:
PerformSpriteDataFetch(5);
break;
case 6:
PerformSpritePointerFetch(6);
break;
case 7:
PerformSpriteDataFetch(6);
break;
case 8:
PerformSpritePointerFetch(7);
break;
case 9:
PerformSpriteDataFetch(7);
break;
case 10:
signal.VicAEC = true;
PerformDRAMRefresh();
break;
case 11:
PerformDRAMRefresh();
break;
case 12:
PerformDRAMRefresh();
break;
case 13:
PerformVCReset();
PerformDRAMRefresh();
break;
case 14:
PerformDRAMRefresh();
break;
case 15:
spriteGenerators[0].Render();
spriteGenerators[1].Render();
spriteGenerators[2].Render();
spriteGenerators[3].Render();
spriteGenerators[4].Render();
spriteGenerators[5].Render();
spriteGenerators[6].Render();
spriteGenerators[7].Render();
PerformSpriteMCBASEAdvance();
PerformScreenCAccess();
break;
case 16:
PerformScreenCAccess();
break;
case 17:
PerformScreenCAccess();
break;
case 18:
PerformScreenCAccess();
break;
case 19:
PerformScreenCAccess();
break;
case 20:
PerformScreenCAccess();
break;
case 21:
PerformScreenCAccess();
break;
case 22:
PerformScreenCAccess();
break;
case 23:
PerformScreenCAccess();
break;
case 24:
PerformScreenCAccess();
break;
case 25:
PerformScreenCAccess();
break;
case 26:
PerformScreenCAccess();
break;
case 27:
PerformScreenCAccess();
break;
case 28:
PerformScreenCAccess();
break;
case 29:
PerformScreenCAccess();
break;
case 30:
PerformScreenCAccess();
break;
case 31:
PerformScreenCAccess();
break;
case 32:
PerformScreenCAccess();
break;
case 33:
PerformScreenCAccess();
break;
case 34:
PerformScreenCAccess();
break;
case 35:
PerformScreenCAccess();
break;
case 36:
PerformScreenCAccess();
break;
case 37:
PerformScreenCAccess();
break;
case 38:
PerformScreenCAccess();
break;
case 39:
PerformScreenCAccess();
break;
case 40:
PerformScreenCAccess();
break;
case 41:
PerformScreenCAccess();
break;
case 42:
PerformScreenCAccess();
break;
case 43:
PerformScreenCAccess();
break;
case 44:
PerformScreenCAccess();
break;
case 45:
PerformScreenCAccess();
break;
case 46:
PerformScreenCAccess();
break;
case 47:
PerformScreenCAccess();
break;
case 48:
PerformScreenCAccess();
break;
case 49:
PerformScreenCAccess();
break;
case 50:
PerformScreenCAccess();
break;
case 51:
PerformScreenCAccess();
break;
case 52:
PerformScreenCAccess();
break;
case 53:
PerformScreenCAccess();
break;
case 54:
PerformScreenCAccess();
PerformSpriteYExpansionFlip();
PerformSpriteComparison();
break;
case 55:
signal.VicAEC = true;
PerformSpriteComparison();
break;
case 56:
break;
case 57:
PerformSpriteDMAEnable();
PerformRCReset();
break;
case 58:
break;
case 59:
PerformSpritePointerFetch(0);
break;
case 60:
PerformSpriteDataFetch(0);
break;
case 61:
PerformSpritePointerFetch(1);
break;
case 62:
PerformSpriteDataFetch(1);
break;
case 63:
PerformSpritePointerFetch(2);
PerformBorderCheck();
break;
case 64:
PerformSpriteDataFetch(2);
break;
}
}
// operations timed to PAL
private void PerformCyclePAL()
{
switch (cycle)
{
case 0:
// rasterline IRQ happens on cycle 1 on rasterline 0
if (regs.RASTER > 0 && regs.RASTER == rasterInterruptLine)
regs.IRST = true;
PerformSpritePointerFetch(3);
break;
case 1:
// rasterline IRQ happens on cycle 1 on rasterline 0
if (regs.RASTER == 0 && regs.RASTER == rasterInterruptLine)
regs.IRST = true;
PerformSpriteDataFetch(3);
break;
case 2:
PerformSpritePointerFetch(4);
break;
case 3:
PerformSpriteDataFetch(4);
break;
case 4:
PerformSpritePointerFetch(5);
break;
case 5:
PerformSpriteDataFetch(5);
break;
case 6:
PerformSpritePointerFetch(6);
break;
case 7:
PerformSpriteDataFetch(6);
break;
case 8:
PerformSpritePointerFetch(7);
break;
case 9:
PerformSpriteDataFetch(7);
break;
case 10:
signal.VicAEC = true;
PerformDRAMRefresh();
break;
case 11:
PerformDRAMRefresh();
break;
case 12:
PerformDRAMRefresh();
break;
case 13:
PerformVCReset();
PerformDRAMRefresh();
break;
case 14:
PerformDRAMRefresh();
break;
case 15:
spriteGenerators[0].Render();
spriteGenerators[1].Render();
spriteGenerators[2].Render();
spriteGenerators[3].Render();
spriteGenerators[4].Render();
spriteGenerators[5].Render();
spriteGenerators[6].Render();
spriteGenerators[7].Render();
PerformSpriteMCBASEAdvance();
PerformScreenCAccess();
break;
case 16:
PerformScreenCAccess();
break;
case 17:
PerformScreenCAccess();
break;
case 18:
PerformScreenCAccess();
break;
case 19:
PerformScreenCAccess();
break;
case 20:
PerformScreenCAccess();
break;
case 21:
PerformScreenCAccess();
break;
case 22:
PerformScreenCAccess();
break;
case 23:
PerformScreenCAccess();
break;
case 24:
PerformScreenCAccess();
break;
case 25:
PerformScreenCAccess();
break;
case 26:
PerformScreenCAccess();
break;
case 27:
PerformScreenCAccess();
break;
case 28:
PerformScreenCAccess();
break;
case 29:
PerformScreenCAccess();
break;
case 30:
PerformScreenCAccess();
break;
case 31:
PerformScreenCAccess();
break;
case 32:
PerformScreenCAccess();
break;
case 33:
PerformScreenCAccess();
break;
case 34:
PerformScreenCAccess();
break;
case 35:
PerformScreenCAccess();
break;
case 36:
PerformScreenCAccess();
break;
case 37:
PerformScreenCAccess();
break;
case 38:
PerformScreenCAccess();
break;
case 39:
PerformScreenCAccess();
break;
case 40:
PerformScreenCAccess();
break;
case 41:
PerformScreenCAccess();
break;
case 42:
PerformScreenCAccess();
break;
case 43:
PerformScreenCAccess();
break;
case 44:
PerformScreenCAccess();
break;
case 45:
PerformScreenCAccess();
break;
case 46:
PerformScreenCAccess();
break;
case 47:
PerformScreenCAccess();
break;
case 48:
PerformScreenCAccess();
break;
case 49:
PerformScreenCAccess();
break;
case 50:
PerformScreenCAccess();
break;
case 51:
PerformScreenCAccess();
break;
case 52:
PerformScreenCAccess();
break;
case 53:
PerformScreenCAccess();
break;
case 54:
PerformScreenCAccess();
PerformSpriteYExpansionFlip();
PerformSpriteComparison();
break;
case 55:
signal.VicAEC = true;
PerformSpriteComparison();
break;
case 56:
break;
case 57:
PerformSpriteDMAEnable();
PerformRCReset();
PerformSpritePointerFetch(0);
break;
case 58:
PerformSpriteDataFetch(0);
break;
case 59:
PerformSpritePointerFetch(1);
break;
case 60:
PerformSpriteDataFetch(1);
break;
case 61:
PerformSpritePointerFetch(2);
break;
case 62:
PerformBorderCheck();
PerformSpriteDataFetch(2);
break;
}
}
private void PerformDRAMRefresh()
{
// dram refresh
mem.VicRead((ushort)refreshAddress);
refreshAddress = (refreshAddress - 1) & 0xFF;
refreshAddress |= 0x3F00;
}
private void PerformRCReset()
{
// row counter processing
if (regs.RC == 7)
{
idle = true;
regs.VCBASE = regs.VC;
}
if (!idle)
regs.RC = (regs.RC + 1) & 0x07;
}
private void PerformScreenCAccess()
{
// screen memory c-access
if (badLine)
{
FetchC();
colorMemory[regs.VMLI] = colorDataBus;
characterMemory[regs.VMLI] = characterDataBus;
}
signal.VicAEC = !badLine;
}
private void PerformSpriteComparison()
{
// sprite comparison
for (int i = 0; i < 8; i++)
{
if (sprites[i].MxE == true && sprites[i].MxY == (regs.RASTER & 0xFF) && sprites[i].MDMA == false)
{
sprites[i].MDMA = true;
sprites[i].MCBASE = 0;
if (sprites[i].MxYE)
sprites[i].MxYEToggle = false;
}
sprites[i].MxXEToggle = false;
}
}
private void PerformSpriteDataFetch(int spriteIndex)
{
// second half of the fetch cycle
signal.VicAEC = !sprites[spriteIndex].MDMA;
if (sprites[spriteIndex].MDMA)
{
for (int i = 0; i < 2; i++)
{
sprites[spriteIndex].MSR <<= 8;
sprites[spriteIndex].MSR |= mem.VicRead((ushort)((sprites[spriteIndex].MPTR << 6) | (sprites[spriteIndex].MC)));
sprites[spriteIndex].MC++;
}
}
}
private void PerformSpriteDMAEnable()
{
// sprite MC processing
for (int i = 0; i < 8; i++)
{
sprites[i].MC = sprites[i].MCBASE;
if (sprites[i].MDMA && sprites[i].MxY == (regs.RASTER & 0xFF))
{
sprites[i].MD = true;
sprites[i].MxXEToggle = false;
}
}
}
private void PerformSpriteMCBASEAdvance()
{
for (int i = 0; i < 8; i++)
{
if (sprites[i].MxYEToggle)
{
sprites[i].MCBASE += 3;
if (sprites[i].MxYEToggle && sprites[i].MCBASE == 63)
{
sprites[i].MD = false;
sprites[i].MDMA = false;
}
}
}
}
private void PerformSpritePointerFetch(int spriteIndex)
{
// first half of the fetch cycle, always fetch pointer
ushort pointerOffset = (ushort)((regs.VM << 10) | 0x3F8 | spriteIndex);
sprites[spriteIndex].MPTR = mem.VicRead(pointerOffset);
// also fetch upper 8 bits if enabled
signal.VicAEC = !sprites[spriteIndex].MDMA;
if (sprites[spriteIndex].MDMA)
{
sprites[spriteIndex].MSRC = 24;
sprites[spriteIndex].MSR = mem.VicRead((ushort)((sprites[spriteIndex].MPTR << 6) | (sprites[spriteIndex].MC)));
sprites[spriteIndex].MC++;
}
}
private void PerformSpriteYExpansionFlip()
{
for (int i = 0; i < 8; i++)
if (sprites[i].MxYE)
sprites[i].MxYEToggle = !sprites[i].MxYEToggle;
}
private void PerformVCReset()
{
// VC reset
regs.VC = regs.VCBASE;
regs.VMLI = 0;
characterColumn = 0;
if (badLine)
{
regs.RC = 0;
}
bitmapData = 0;
colorData = 0;
characterData = 0;
}
public void Poke(int addr, byte val)
{
regs[addr & 0x3F] = val;
}
// standard text mode
private int Plot000()
{
if (characterColumn >= 0)
{
byte charData = bitmapData;
charData <<= characterColumn;
if ((charData & 0x80) != 0x00)
{
dataForeground = true;
return colorData;
}
}
dataForeground = false;
return regs.BxC[0];
}
// multicolor text mode
private int Plot001()
{
if (characterColumn >= 0)
{
if ((colorData & 0x08) != 0x00)
{
int offset = characterColumn;
byte charData = bitmapData;
offset |= 0x01;
offset ^= 0x01;
charData <<= offset;
charData >>= 6;
switch (charData)
{
case 1:
dataForeground = false;
return regs.BxC[1];
case 2:
dataForeground = true;
return regs.BxC[2];
case 3:
dataForeground = true;
return colorData & 0x07;
}
}
else
{
return Plot000();
}
}
dataForeground = false;
return regs.BxC[0];
}
// standard bitmap mode
private int Plot010()
{
if (characterColumn >= 0)
{
byte charData = bitmapData;
charData <<= characterColumn;
if ((charData & 0x80) != 0x00)
{
dataForeground = true;
return characterData >> 4;
}
}
dataForeground = false;
return characterData & 0xF;
}
// multicolor bitmap mode
private int Plot011()
{
if (characterColumn >= 0)
{
int offset = characterColumn;
byte charData = bitmapData;
offset |= 0x01;
offset ^= 0x01;
charData <<= offset;
charData >>= 6;
switch (charData)
{
case 1:
dataForeground = false;
return characterData >> 4;
case 2:
dataForeground = true;
return characterData & 0xF;
case 3:
dataForeground = true;
return colorData & 0xF;
}
}
dataForeground = false;
return regs.BxC[0];
}
// extra color text mode
private int Plot100()
{
if (characterColumn >= 0)
{
byte charData = bitmapData;
charData <<= characterColumn;
if ((charData & 0x80) != 0x00)
{
dataForeground = true;
return colorData;
}
else
{
dataForeground = false;
return regs.BxC[characterData >> 6];
}
}
dataForeground = false;
return regs.BxC[0];
}
// invalid mode (TODO: implement collision)
// this mode always outputs black
private int Plot101()
{
dataForeground = false;
return 0;
}
// invalid mode (TODO: implement collision)
// this mode always outputs black
private int Plot110()
{
dataForeground = false;
return 0;
}
// invalid mode (TODO: implement collision)
// this mode always outputs black
private int Plot111()
{
dataForeground = false;
return 0;
}
public byte Read(ushort addr)
{
byte result = 0;
addr &= 0x3F;
switch (addr)
{
case 0x1E:
// clear after read
result = regs[addr];
regs[addr] = 0x00;
regs.IMMC = false;
break;
case 0x1F:
// clear after read
result = regs[addr];
regs[addr] = 0x00;
regs.IMBC = false;
break;
default:
result = regs[addr];
break;
}
return result;
}
private void RenderCycle()
{
int inputPixel;
int outputPixel;
int spriteBits;
int spritePixel;
int spritePixelOwner;
for (int i = 0; i < 8; i++)
{
spritePixelOwner = -1;
// draw screen memory if needed
if (!idle && cycle >= 15 && cycle < 55)
{
if (regs.XSCROLL == i)
{
characterColumn = 0;
characterData = characterMemory[regs.VMLI];
colorData = colorMemory[regs.VMLI];
FetchG();
regs.VC++;
regs.VMLI++;
}
}
// check to see if we are within viewing area X
if (rasterOffsetX == visibleRight)
visibleRenderX = false;
if (rasterOffsetX == visibleLeft)
visibleRenderX = true;
// check to see if we are at the border
if (rasterOffsetX == borderRight)
borderOnMain = true;
if (rasterOffsetX == borderLeft)
{
if (regs.RASTER == borderBottom)
borderOnVertical = true;
if ((regs.RASTER == borderTop) && regs.DEN)
borderOnVertical = false;
if (!borderOnVertical)
borderOnMain = false;
}
// render plotter
if (idle)
inputPixel = regs.BxC[0];
else
inputPixel = Plotter();
// render sprites
outputPixel = pixelBuffer[pixelBufferIndex];
for (int j = 0; j < 8; j++)
{
if (spriteGenerators[j].hasData)
{
if (spriteGenerators[j].dataBuffer[rasterOffsetX] != 0)
{
// process collisions if the border is off
if (!borderOnVertical)
{
if (spritePixelOwner == -1)
{
spritePixelOwner = j;
if (!sprites[j].MxDP || (!pixelBufferForeground[pixelBufferIndex]))
{
outputPixel = spriteGenerators[j].colorBuffer[rasterOffsetX];
}
}
else
{
// a sprite already occupies this space
sprites[spritePixelOwner].MxM = true;
sprites[j].MxM = true;
regs.IMMC = true;
}
if (pixelBufferForeground[pixelBufferIndex])
{
sprites[j].MxD = true;
regs.IMBC = true;
}
}
}
}
}
// send pixel to bitmap
if (visibleRenderX && visibleRenderY)
{
if (borderOnMain || borderOnVertical)
{
WritePixel(regs.EC);
}
else
{
WritePixel(outputPixel);
}
}
// process pixel delay
pixelBuffer[pixelBufferIndex] = inputPixel;
pixelBufferForeground[pixelBufferIndex] = dataForeground;
pixelBufferIndex++;
if (pixelBufferIndex == pixelBufferLength)
{
pixelBufferIndex = 0;
}
// advance raster X
characterColumn++;
rasterOffset++;
rasterOffsetX++;
if (rasterOffsetX == rasterWidth)
{
rasterOffsetX -= rasterWidth;
}
}
}
private void UpdateBorder()
{
borderTop = regs.RSEL ? 0x033 : 0x037;
borderBottom = regs.RSEL ? 0x0FB : 0x0F7;
borderLeft = regs.CSEL ? 0x018 : 0x01F;
borderRight = regs.CSEL ? 0x158 : 0x14F;
}
private void UpdateInterrupts()
{
// check for anything that would've triggered an interrupt and raise the flag if so
regs.IRQ = ((regs.IRST & regs.ERST) || (regs.IMMC & regs.EMMC) || (regs.IMBC & regs.EMBC) || (regs.ILP & regs.ELP));
}
private void UpdatePlotter()
{
// determine the plot and fetch mode
if (!regs.ECM && !regs.BMM && !regs.MCM)
{
FetchC = Fetch000C;
FetchG = Fetch000G;
Plotter = Plot000;
}
else if (!regs.ECM && !regs.BMM && regs.MCM)
{
FetchC = Fetch000C;
FetchG = Fetch000G;
Plotter = Plot001;
}
else if (!regs.ECM && regs.BMM && !regs.MCM)
{
FetchC = Fetch000C;
FetchG = Fetch010G;
Plotter = Plot010;
}
else if (!regs.ECM && regs.BMM && regs.MCM)
{
FetchC = Fetch000C;
FetchG = Fetch010G;
Plotter = Plot011;
}
else if (regs.ECM && !regs.BMM && !regs.MCM)
{
FetchC = Fetch000C;
FetchG = Fetch100G;
Plotter = Plot100;
}
else if (regs.ECM && !regs.BMM && regs.MCM)
{
FetchC = Fetch000C;
FetchG = Fetch100G;
Plotter = Plot101;
}
else if (regs.ECM && regs.BMM && !regs.MCM)
{
FetchC = Fetch000C;
FetchG = Fetch110G;
Plotter = Plot110;
}
else
{
FetchC = Fetch000C;
FetchG = Fetch110G;
Plotter = Plot111;
}
}
public void Write(ushort addr, byte val)
{
addr &= 0x3F;
switch (addr)
{
case 0x11:
rasterInterruptLine &= 0xFF;
rasterInterruptLine |= (val & 0x80) << 1;
// raster upper bit can't be changed, save and restore the value
val &= 0x7F;
val |= (byte)(regs[addr] & 0x80);
regs[addr] = val;
UpdateBorder();
UpdatePlotter();
break;
case 0x12:
// raster interrupt lower 8 bits
rasterInterruptLine &= 0x100;
rasterInterruptLine |= (val & 0xFF);
break;
case 0x16:
regs[addr] = val;
UpdateBorder();
UpdatePlotter();
break;
case 0x19:
// only allow clearing of these flags
if ((val & 0x01) != 0x00)
regs.IRST = false;
if ((val & 0x02) != 0x00)
regs.IMBC = false;
if ((val & 0x04) != 0x00)
regs.IMMC = false;
if ((val & 0x08) != 0x00)
regs.ILP = false;
UpdateInterrupts();
break;
case 0x1E:
case 0x1F:
// can't write to these regs
break;
default:
regs[addr] = val;
break;
}
}
private void WritePixel(int value)
{
buffer[renderOffset++] = palette[value];
}
}
}