572 lines
16 KiB
C#
572 lines
16 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Globalization;
|
|
|
|
using BizHawk.Common;
|
|
using BizHawk.Common.BufferExtensions;
|
|
using BizHawk.Common.IOExtensions;
|
|
using BizHawk.Emulation.Common;
|
|
|
|
namespace BizHawk.Emulation.Cores.Sega.Genesis
|
|
{
|
|
public sealed partial class GenVDP : IVideoProvider
|
|
{
|
|
// Memory
|
|
public byte[] VRAM = new byte[0x10000];
|
|
public ushort[] CRAM = new ushort[64];
|
|
public ushort[] VSRAM = new ushort[40];
|
|
public byte[] Registers = new byte[0x20];
|
|
|
|
public byte[] PatternBuffer = new byte[0x20000];
|
|
public int[] Palette = new int[64];
|
|
public int[] FrameBuffer = new int[320 * 224];
|
|
public int FrameWidth = 320;
|
|
public int FrameHeight = 224;
|
|
|
|
public int ScanLine;
|
|
public int HIntLineCounter;
|
|
|
|
public bool HInterruptsEnabled { get { return (Registers[0] & 0x10) != 0; } }
|
|
public bool DisplayEnabled { get { return (Registers[1] & 0x40) != 0; } }
|
|
public bool VInterruptEnabled { get { return (Registers[1] & 0x20) != 0; } }
|
|
public bool DmaEnabled { get { return (Registers[1] & 0x10) != 0; } }
|
|
public bool CellBasedVertScroll { get { return (Registers[11] & 0x04) != 0; } }
|
|
|
|
public bool InDisplayPeriod { get { return ScanLine < 224 && DisplayEnabled; } }
|
|
|
|
ushort NameTableAddrA;
|
|
ushort NameTableAddrB;
|
|
ushort NameTableAddrWindow;
|
|
ushort SpriteAttributeTableAddr;
|
|
ushort HScrollTableAddr;
|
|
int NameTableWidth = 32;
|
|
int NameTableHeight = 32;
|
|
|
|
int DisplayWidth;
|
|
int SpriteLimit;
|
|
int SpritePerLineLimit;
|
|
int DotsPerLineLimit;
|
|
|
|
bool ControlWordPending;
|
|
ushort VdpDataAddr;
|
|
byte VdpDataCode;
|
|
|
|
const int CommandVramRead = 0;
|
|
const int CommandVramWrite = 1;
|
|
const int CommandCramWrite = 3;
|
|
const int CommandVsramRead = 4;
|
|
const int CommandVsramWrite = 5;
|
|
const int CommandCramRead = 8;
|
|
|
|
public ushort VdpStatusWord = 0x3400;
|
|
public const int StatusHorizBlanking = 0x04;
|
|
public const int StatusVerticalBlanking = 0x08;
|
|
public const int StatusOddFrame = 0x10;
|
|
public const int StatusSpriteCollision = 0x20;
|
|
public const int StatusSpriteOverflow = 0x40;
|
|
public const int StatusVerticalInterruptPending = 0x80;
|
|
|
|
public bool VdpDebug = false;
|
|
|
|
public Func<int> GetPC;
|
|
|
|
public GenVDP()
|
|
{
|
|
WriteVdpRegister(00, 0x04);
|
|
WriteVdpRegister(01, 0x04);
|
|
WriteVdpRegister(02, 0x30);
|
|
WriteVdpRegister(03, 0x3C);
|
|
WriteVdpRegister(04, 0x07);
|
|
WriteVdpRegister(05, 0x67);
|
|
WriteVdpRegister(10, 0xFF);
|
|
WriteVdpRegister(12, 0x81);
|
|
WriteVdpRegister(15, 0x02);
|
|
Log.Note("VDP", "VDP init routine complete");
|
|
}
|
|
|
|
public ushort ReadVdp(int addr)
|
|
{
|
|
switch (addr)
|
|
{
|
|
case 0:
|
|
case 2:
|
|
return ReadVdpData();
|
|
case 4:
|
|
case 6:
|
|
return ReadVdpControl();
|
|
default:
|
|
return ReadHVCounter();
|
|
}
|
|
}
|
|
|
|
public void WriteVdp(int addr, ushort data)
|
|
{
|
|
switch (addr)
|
|
{
|
|
case 0:
|
|
case 2:
|
|
WriteVdpData(data);
|
|
return;
|
|
case 4:
|
|
case 6:
|
|
WriteVdpControl(data);
|
|
return;
|
|
}
|
|
}
|
|
|
|
public void WriteVdpControl(ushort data)
|
|
{
|
|
Log.Note("VDP", "Control Write {0:X4} (PC={1:X6})", data, GetPC());
|
|
|
|
if (ControlWordPending == false)
|
|
{
|
|
if ((data & 0xC000) == 0x8000)
|
|
{
|
|
int reg = (data >> 8) & 0x1F;
|
|
byte value = (byte)(data & 0xFF);
|
|
WriteVdpRegister(reg, value);
|
|
VdpDataCode = 0;
|
|
}
|
|
else
|
|
{
|
|
ControlWordPending = true;
|
|
VdpDataAddr &= 0xC000;
|
|
VdpDataAddr |= (ushort)(data & 0x3FFF);
|
|
VdpDataCode &= 0x3C;
|
|
VdpDataCode |= (byte)(data >> 14);
|
|
//Console.WriteLine("Address = {0:X4}", VdpDataAddr);
|
|
//Console.WriteLine("Code = {0:X2}", VdpDataCode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ControlWordPending = false;
|
|
|
|
// Update data address and code
|
|
VdpDataAddr &= 0x3FFF;
|
|
VdpDataAddr |= (ushort)((data & 0x03) << 14);
|
|
//Console.WriteLine("Address = {0:X4}", VdpDataAddr);
|
|
VdpDataCode &= 0x03;
|
|
VdpDataCode |= (byte)((data >> 2) & 0x3C);
|
|
//Log.Note("VDP", "Code = {0:X2}", VdpDataCode);
|
|
|
|
if ((VdpDataCode & 0x20) != 0 && DmaEnabled) // DMA triggered
|
|
{
|
|
//Console.WriteLine("DMA TIME!");
|
|
|
|
// what type of DMA?
|
|
switch (Registers[23] >> 6)
|
|
{
|
|
case 2:
|
|
Log.Note("VDP", "VRAM FILL");
|
|
DmaFillModePending = true;
|
|
break;
|
|
case 3:
|
|
Log.Error("VDP", "VRAM COPY *");
|
|
ExecuteVramVramCopy();
|
|
break;
|
|
default:
|
|
Execute68000VramCopy();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public ushort ReadVdpControl()
|
|
{
|
|
VdpStatusWord |= 0x0200; // Fifo empty // TODO kill this, emulating the damn FIFO.
|
|
ControlWordPending = false; // Hmm.. if this happens in an interrupt between 1st and 2nd word..
|
|
|
|
// sprite overflow flag should clear.
|
|
// sprite collision flag should clear.
|
|
|
|
return VdpStatusWord;
|
|
}
|
|
|
|
public void WriteVdpData(ushort data)
|
|
{
|
|
Log.Note("VDP", "Data port write: {0:X4} (PC={1:X6})", data, GetPC());
|
|
ControlWordPending = false;
|
|
|
|
// byte-swap incoming data when A0 is set
|
|
if ((VdpDataAddr & 1) != 0)
|
|
{
|
|
data = (ushort)((data >> 8) | (data << 8));
|
|
Log.Error("VDP", "VRAM byte-swap is happening because A0 is not 0. [{0:X4}] = {1:X4}", VdpDataAddr, data);
|
|
}
|
|
|
|
switch (VdpDataCode & 0xF)
|
|
{
|
|
case CommandVramWrite: // VRAM Write
|
|
VRAM[VdpDataAddr & 0xFFFE] = (byte)data;
|
|
VRAM[(VdpDataAddr & 0xFFFE) + 1] = (byte)(data >> 8);
|
|
//if (VdpDebug)
|
|
Log.Note("VDP", "VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data);
|
|
UpdatePatternBuffer(VdpDataAddr & 0xFFFE);
|
|
UpdatePatternBuffer((VdpDataAddr & 0xFFFE) + 1);
|
|
VdpDataAddr += Registers[0x0F];
|
|
break;
|
|
case CommandCramWrite: // CRAM write
|
|
CRAM[(VdpDataAddr / 2) % 64] = data;
|
|
//if (VdpDebug)
|
|
Log.Note("VDP", "CRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 64, data);
|
|
ProcessPalette((VdpDataAddr / 2) % 64);
|
|
VdpDataAddr += Registers[0x0F];
|
|
break;
|
|
case CommandVsramWrite: // VSRAM write
|
|
VSRAM[(VdpDataAddr / 2) % 40] = data;
|
|
//if (VdpDebug)
|
|
Log.Note("VDP", "VSRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 40, data);
|
|
VdpDataAddr += Registers[0x0F];
|
|
break;
|
|
default:
|
|
Log.Error("VPD", "VDP DATA WRITE WITH UNHANDLED CODE!!! {0}", VdpDataCode & 7);
|
|
break;
|
|
}
|
|
|
|
if (DmaFillModePending)
|
|
{
|
|
ExecuteVramFill(data);
|
|
}
|
|
}
|
|
|
|
public ushort ReadVdpData()
|
|
{
|
|
int orig_addr = VdpDataAddr;
|
|
ushort retval = 0xBEEF;
|
|
switch (VdpDataCode & 0x0F)
|
|
{
|
|
case CommandVramRead:
|
|
//if ((VdpDataAddr & 1) != 0) throw new Exception("VRAM read is not word-aligned. what do?");
|
|
retval = VRAM[VdpDataAddr & 0xFFFE];
|
|
retval |= (ushort)(VRAM[(VdpDataAddr & 0xFFFE) + 1] << 8);
|
|
VdpDataAddr += Registers[0x0F];
|
|
break;
|
|
case CommandVsramRead:
|
|
retval = VSRAM[(VdpDataAddr / 2) % 40];
|
|
VdpDataAddr += Registers[0x0F];
|
|
return retval;
|
|
case CommandCramRead:
|
|
retval = CRAM[(VdpDataAddr / 2) % 64];
|
|
VdpDataAddr += Registers[0x0F];
|
|
return retval;
|
|
default:
|
|
throw new Exception("VRAM read with unexpected code!!! " + (VdpDataCode & 0x0F));
|
|
}
|
|
|
|
Log.Note("VDP", "VDP Data Read from {0:X4} returning {1:X4}", orig_addr, retval);
|
|
return retval;
|
|
}
|
|
|
|
ushort ReadHVCounter()
|
|
{
|
|
int vcounter = ScanLine;
|
|
if (vcounter > 0xEA)
|
|
vcounter -= 7;
|
|
// TODO generalize this across multiple video modes and stuff.
|
|
|
|
// TODO dont tie this to musashi cycle count.
|
|
// Figure out a "clean" way to get cycle counter information available to VDP.
|
|
// Oh screw that. The VDP and the cpu cycle counters are going to be intertwined pretty tightly.
|
|
int hcounter = (488 - Native68000.Musashi.GetCyclesRemaining()) * 255 / 488;
|
|
// FIXME: totally utterly wrong.
|
|
|
|
ushort res = (ushort)((vcounter << 8) | (hcounter & 0xFF));
|
|
//Console.WriteLine("READ HVC: V={0:X2} H={1:X2} ret={2:X4}", vcounter, hcounter, res);
|
|
|
|
return res;
|
|
}
|
|
|
|
public void WriteVdpRegister(int register, byte data)
|
|
{
|
|
//if (VdpDebug)
|
|
Log.Note("VDP", "Register {0}: {1:X2}", register, data);
|
|
switch (register)
|
|
{
|
|
case 0x00: // Mode Set Register 1
|
|
Registers[register] = data;
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "HINT enabled: " + HInterruptsEnabled);
|
|
break;
|
|
|
|
case 0x01: // Mode Set Register 2
|
|
//if (VdpDebug)
|
|
//{
|
|
// Registers[register] = data;
|
|
// Log.Note("VDP", "DisplayEnabled: " + DisplayEnabled);
|
|
// Log.Note("VDP", "DmaEnabled: " + DmaEnabled);
|
|
// Log.Note("VDP", "VINT enabled: " + VInterruptEnabled);
|
|
//}
|
|
break;
|
|
|
|
case 0x02: // Name Table Address for Layer A
|
|
NameTableAddrA = (ushort)((data & 0x38) << 10);
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "SET NTa A = {0:X4}", NameTableAddrA);
|
|
break;
|
|
|
|
case 0x03: // Name Table Address for Window
|
|
NameTableAddrWindow = (ushort)((data & 0x3E) << 10);
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow);
|
|
break;
|
|
|
|
case 0x04: // Name Table Address for Layer B
|
|
NameTableAddrB = (ushort)(data << 13);
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "SET NTa B = {0:X4}", NameTableAddrB);
|
|
break;
|
|
|
|
case 0x05: // Sprite Attribute Table Address
|
|
SpriteAttributeTableAddr = (ushort)(data << 9);
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr);
|
|
break;
|
|
|
|
case 0x0A: // H Interrupt Register
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "HInt occurs every {0} lines.", data);
|
|
break;
|
|
|
|
case 0x0B: // VScroll/HScroll modes
|
|
//if (VdpDebug)
|
|
//{
|
|
// if ((data & 4) != 0)
|
|
// Log.Note("VDP", "VSCroll Every 2 Cells Enabled");
|
|
// else
|
|
// Log.Note("VDP", "Full Screen VScroll");
|
|
|
|
// int hscrollmode = data & 3;
|
|
// switch (hscrollmode)
|
|
// {
|
|
// case 0: Log.Note("VDP", "Full Screen HScroll"); break;
|
|
// case 1: Log.Note("VDP", "Prohibited HSCROLL mode!!! But it'll work."); break;
|
|
// case 2: Log.Note("VDP", "HScroll every 1 cell"); break;
|
|
// case 3: Log.Note("VDP", "HScroll every line"); break;
|
|
// }
|
|
//}
|
|
break;
|
|
|
|
case 0x0C: // Mode Set #4
|
|
// TODO interlaced modes
|
|
if ((data & 0x81) == 0)
|
|
{
|
|
// Display is 32 cells wide
|
|
if (DisplayWidth != 32)
|
|
{
|
|
FrameBuffer = new int[256 * 224];
|
|
FrameWidth = 256;
|
|
DisplayWidth = 32;
|
|
SpriteLimit = 64;
|
|
SpritePerLineLimit = 16;
|
|
DotsPerLineLimit = 256;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Display is 40 cells wide
|
|
if (DisplayWidth != 40)
|
|
{
|
|
FrameBuffer = new int[320 * 224];
|
|
FrameWidth = 320;
|
|
DisplayWidth = 40;
|
|
SpriteLimit = 80;
|
|
SpritePerLineLimit = 20;
|
|
DotsPerLineLimit = 320;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0x0D: // H Scroll Table Address
|
|
HScrollTableAddr = (ushort)(data << 10);
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "SET HScrollTab attr = {0:X4}", HScrollTableAddr);
|
|
break;
|
|
|
|
case 0x0F: // Auto Address Register Increment
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "Set Data Increment to " + data);
|
|
break;
|
|
|
|
case 0x10: // Nametable Dimensions
|
|
switch (data & 0x03)
|
|
{
|
|
case 0: NameTableWidth = 32; break;
|
|
case 1: NameTableWidth = 64; break;
|
|
case 2: NameTableWidth = 32; break; // invalid setting
|
|
case 3: NameTableWidth = 128; break;
|
|
}
|
|
switch ((data >> 4) & 0x03)
|
|
{
|
|
case 0: NameTableHeight = 32; break;
|
|
case 1: NameTableHeight = 64; break;
|
|
case 2: NameTableHeight = 32; break; // invalid setting
|
|
case 3: NameTableHeight = 128; break;
|
|
}
|
|
break;
|
|
|
|
case 0x11: // Window H Position
|
|
int whp = data & 31;
|
|
bool fromright = (data & 0x80) != 0;
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "Window H is {0} units from {1}", whp, fromright ? "right" : "left");
|
|
break;
|
|
|
|
case 0x12: // Window V
|
|
whp = data & 31;
|
|
fromright = (data & 0x80) != 0;
|
|
//if (VdpDebug)
|
|
//Log.Note("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper");
|
|
break;
|
|
|
|
case 0x13: // DMA Length Low
|
|
Registers[register] = data;
|
|
//Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
|
|
break;
|
|
|
|
case 0x14: // DMA Length High
|
|
Registers[register] = data;
|
|
//Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
|
|
break;
|
|
|
|
case 0x15: // DMA Source Low
|
|
Registers[register] = data;
|
|
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
|
break;
|
|
case 0x16: // DMA Source Mid
|
|
Registers[register] = data;
|
|
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
|
break;
|
|
case 0x17: // DMA Source High
|
|
Registers[register] = data;
|
|
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
|
break;
|
|
|
|
}
|
|
Registers[register] = data;
|
|
}
|
|
|
|
void ProcessPalette(int slot)
|
|
{
|
|
byte r = PalXlatTable[(CRAM[slot] & 0x000F) >> 0];
|
|
byte g = PalXlatTable[(CRAM[slot] & 0x00F0) >> 4];
|
|
byte b = PalXlatTable[(CRAM[slot] & 0x0F00) >> 8];
|
|
Palette[slot] = Colors.ARGB(r, g, b);
|
|
}
|
|
|
|
void UpdatePatternBuffer(int addr)
|
|
{
|
|
PatternBuffer[(addr * 2) + 1] = (byte)(VRAM[addr ^ 1] & 0x0F);
|
|
PatternBuffer[(addr * 2) + 0] = (byte)(VRAM[addr ^ 1] >> 4);
|
|
}
|
|
|
|
public int[] GetVideoBuffer()
|
|
{
|
|
return FrameBuffer;
|
|
}
|
|
|
|
public int VirtualWidth { get { return 320; } }
|
|
public int VirtualHeight { get { return FrameHeight; } }
|
|
|
|
public int BufferWidth
|
|
{
|
|
get { return FrameWidth; }
|
|
}
|
|
|
|
public int BufferHeight
|
|
{
|
|
get { return FrameHeight; }
|
|
}
|
|
|
|
public int BackgroundColor
|
|
{
|
|
get { return Palette[Registers[7] & 0x3F]; }
|
|
}
|
|
|
|
#region State Save/Load Code
|
|
|
|
public void SaveStateText(TextWriter writer)
|
|
{
|
|
writer.WriteLine("[VDP]");
|
|
|
|
writer.Write("VRAM ");
|
|
VRAM.SaveAsHex(writer);
|
|
writer.Write("CRAM ");
|
|
CRAM.SaveAsHex(writer);
|
|
writer.Write("VSRAM ");
|
|
VSRAM.SaveAsHex(writer);
|
|
writer.Write("Registers ");
|
|
Registers.SaveAsHex(writer);
|
|
|
|
writer.WriteLine("ControlWordPending {0}", ControlWordPending);
|
|
writer.WriteLine("DmaFillModePending {0}", DmaFillModePending);
|
|
writer.WriteLine("VdpDataAddr {0:X4}", VdpDataAddr);
|
|
writer.WriteLine("VdpDataCode {0}", VdpDataCode);
|
|
|
|
writer.WriteLine("[/VDP]");
|
|
}
|
|
|
|
public void LoadStateText(TextReader reader)
|
|
{
|
|
while (true)
|
|
{
|
|
string[] args = reader.ReadLine().Split(' ');
|
|
if (args[0].Trim() == "") continue;
|
|
if (args[0] == "[/VDP]") break;
|
|
else if (args[0] == "VRAM") VRAM.ReadFromHex(args[1]);
|
|
else if (args[0] == "CRAM") CRAM.ReadFromHex(args[1]);
|
|
else if (args[0] == "VSRAM") VSRAM.ReadFromHex(args[1]);
|
|
else if (args[0] == "Registers") Registers.ReadFromHex(args[1]);
|
|
else if (args[0] == "ControlWordPending") ControlWordPending = bool.Parse(args[1]);
|
|
else if (args[0] == "DmaFillModePending") DmaFillModePending = bool.Parse(args[1]);
|
|
else if (args[0] == "VdpDataAddr") VdpDataAddr = ushort.Parse(args[1], NumberStyles.HexNumber);
|
|
else if (args[0] == "VdpDataCode") VdpDataCode = byte.Parse(args[1]);
|
|
else
|
|
Console.WriteLine("Skipping unrecognized identifier " + args[0]);
|
|
}
|
|
|
|
for (int i = 0; i < CRAM.Length; i++)
|
|
ProcessPalette(i);
|
|
for (int i = 0; i < VRAM.Length; i++)
|
|
UpdatePatternBuffer(i);
|
|
for (int i = 0; i < Registers.Length; i++)
|
|
WriteVdpRegister(i, Registers[i]);
|
|
}
|
|
|
|
public void SaveStateBinary(BinaryWriter writer)
|
|
{
|
|
writer.Write(VRAM);
|
|
writer.Write(CRAM);
|
|
writer.Write(VSRAM);
|
|
writer.Write(Registers);
|
|
|
|
writer.Write(ControlWordPending);
|
|
writer.Write(DmaFillModePending);
|
|
writer.Write(VdpDataAddr);
|
|
writer.Write(VdpDataCode);
|
|
}
|
|
|
|
public void LoadStateBinary(BinaryReader reader)
|
|
{
|
|
VRAM = reader.ReadBytes(VRAM.Length);
|
|
CRAM = reader.ReadUInt16s(CRAM.Length);
|
|
VSRAM = reader.ReadUInt16s(VSRAM.Length);
|
|
Registers = reader.ReadBytes(Registers.Length);
|
|
|
|
ControlWordPending = reader.ReadBoolean();
|
|
DmaFillModePending = reader.ReadBoolean();
|
|
VdpDataAddr = reader.ReadUInt16();
|
|
VdpDataCode = reader.ReadByte();
|
|
|
|
for (int i = 0; i < CRAM.Length; i++)
|
|
ProcessPalette(i);
|
|
for (int i = 0; i < VRAM.Length; i++)
|
|
UpdatePatternBuffer(i);
|
|
for (int i = 0; i < Registers.Length; i++)
|
|
WriteVdpRegister(i, Registers[i]);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |