Updated VDP to handle GameGear. Added Turbo mode. Versio 1.0 me thinks!
This commit is contained in:
@@ -23,7 +23,7 @@ namespace Core
|
||||
//public const int TStatesPerFrame = 49780; //PAL
|
||||
|
||||
public SmsMachine()
|
||||
{
|
||||
{
|
||||
MemoryBus = new SmsMemoryBus();
|
||||
VideoProcessor = new SmsVdp();
|
||||
AudioProcessor = new SmsApu();
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Core.Video
|
||||
{
|
||||
public class SmsVdp
|
||||
{
|
||||
public bool IsPalRegion { get; set; } = false;
|
||||
// The VDP's private memory! The CPU cannot touch these arrays directly.
|
||||
public byte[] VRAM { get; private set; } = new byte[0x4000]; // 16KB Video RAM
|
||||
public byte[] CRAM { get; private set; } = new byte[0x20]; // 32 Bytes Color Palette
|
||||
public byte[] smsCRAM { get; private set; } = new byte[0x20]; // Master System - 32 Bytes colour Palette
|
||||
public byte[] ggCRAM { get; private set; } = new byte[0x40]; // GameGear - 64 Bytes colour palette
|
||||
public byte[] Registers { get; private set; } = new byte[16]; // 11 Hardware Control Registers
|
||||
public int[] FrameBuffer { get; private set; } = new int[256 * 192];
|
||||
private bool[] _priorityBuffer = new bool[256 * 192]; // Tracks priority pixels!
|
||||
@@ -26,11 +27,14 @@ namespace Core.Video
|
||||
private int _currentScanline = 0;
|
||||
private int _lineCounter = 0;
|
||||
private byte _statusRegister = 0x00;
|
||||
public bool IsGameGear { get; set; } = false;
|
||||
|
||||
public bool InterruptPending =>
|
||||
((_statusRegister & 0x80) != 0 && (Registers[1] & 0x20) != 0) || // VBlank
|
||||
((_statusRegister & 0x40) != 0 && (Registers[0] & 0x10) != 0); // Line Interrupt
|
||||
|
||||
|
||||
|
||||
public byte ReadDataPort() // Port 0xBE
|
||||
{
|
||||
_isSecondControlByte = false; // Reading data resets the control latch
|
||||
@@ -45,8 +49,8 @@ namespace Core.Video
|
||||
_isSecondControlByte = false;
|
||||
byte currentStatus = _statusRegister;
|
||||
|
||||
// CRITICAL HARDWARE QUIRK: Reading the status port physically
|
||||
// clears the flags inside the chip! If we don't clear this,
|
||||
// Reading the status port physically
|
||||
// clears the flags inside the chip If we don't clear this,
|
||||
// the interrupt line gets stuck on forever.
|
||||
_statusRegister = 0x00;
|
||||
|
||||
@@ -56,20 +60,29 @@ namespace Core.Video
|
||||
public void WriteDataPort(byte value) // Port 0xBE
|
||||
{
|
||||
_isSecondControlByte = false;
|
||||
_readBuffer = value;
|
||||
_readBuffer = value;
|
||||
|
||||
int address = _controlWord & 0x3FFF;
|
||||
int command = (_controlWord >> 14) & 0x03;
|
||||
|
||||
if (command == 3) // Code 3: Write to Color Palette (CRAM)
|
||||
if (command == 3) // Code 3: Write to Colour Palette (CRAM)
|
||||
{
|
||||
CRAM[address & 0x1F] = value;
|
||||
if (IsGameGear)
|
||||
{
|
||||
ggCRAM[address & 0x3F] = value; // GG has 64 bytes of CRAM
|
||||
}
|
||||
else
|
||||
{
|
||||
smsCRAM[address & 0x1F] = value; // SMS has 32 bytes of CRAM
|
||||
}
|
||||
}
|
||||
else // Code 0, 1, 2: Write to VRAM
|
||||
else // THE FIX: Code 0, 1, 2: Write graphics to VRAM!
|
||||
{
|
||||
VRAM[address] = value;
|
||||
}
|
||||
_controlWord++; // Auto-increment so the Z80 can blast data fast
|
||||
|
||||
// THE FIX: The pointer MUST auto-increment so the CPU can blast data fast!
|
||||
_controlWord++;
|
||||
}
|
||||
|
||||
public void WriteControlPort(byte value) // Port 0xBF
|
||||
@@ -105,23 +118,14 @@ namespace Core.Video
|
||||
|
||||
public byte ReadVCounter()
|
||||
{
|
||||
if (IsPalRegion)
|
||||
{
|
||||
// PAL Math: 313 lines. Counts 0 to 242, jumps to 186 (0xBA), counts to 255.
|
||||
if (_currentScanline <= 242) return (byte)_currentScanline;
|
||||
else return (byte)(_currentScanline - 57);
|
||||
}
|
||||
else
|
||||
{
|
||||
// NTSC Math: 262 lines. Counts 0 to 218, jumps to 213 (0xD5), counts to 255.
|
||||
// NTSC Math: 262 lines. Counts 0 to 218, jumps to 213 (0xD5), counts to 255.
|
||||
if (_currentScanline <= 218) return (byte)_currentScanline;
|
||||
else return (byte)(_currentScanline - 6);
|
||||
}
|
||||
|
||||
}
|
||||
public byte ReadHCounter()
|
||||
{
|
||||
// The Master System H-Counter is a notoriously weird 8-bit timer.
|
||||
// It counts from 0x00 to 0x93, then jumps forward to 0xE9, ending at 0xFF.
|
||||
// The Master System H-Counter counts from 0x00 to 0x93, then jumps forward to 0xE9, ending at 0xFF.
|
||||
|
||||
// 1 T-State = 1.5 pixels. The H-Counter increments every 2 pixels.
|
||||
// So H = T * 0.75
|
||||
@@ -173,7 +177,7 @@ namespace Core.Video
|
||||
// 3. MOVE TO THE NEXT LINE
|
||||
_currentScanline++;
|
||||
|
||||
int maxLines = IsPalRegion ? 313 : 262;
|
||||
int maxLines = 262;
|
||||
|
||||
if (_currentScanline > maxLines -1)
|
||||
{
|
||||
@@ -197,7 +201,6 @@ namespace Core.Video
|
||||
for (int x = 0; x < 256; x++) FrameBuffer[(screenY * 256) + x] = unchecked((int)0xFF000000);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 1. RENDER BACKGROUND LINE ---
|
||||
ushort nameTableBase = (ushort)((Registers[2] & 0x0E) << 10);
|
||||
int scrollX = Registers[8];
|
||||
@@ -213,16 +216,10 @@ namespace Core.Video
|
||||
// --- LEFT COLUMN MASKING (OVERSCAN CURTAIN) ---
|
||||
if (maskLeftCol && screenX < 8)
|
||||
{
|
||||
// Draw the physical backdrop color (from Sprite Palette + Reg 7 index)
|
||||
byte bgSmsColor = CRAM[16 + (Registers[7] & 0x0F)];
|
||||
int bgR = (bgSmsColor & 0x03) * 85;
|
||||
int bgG = ((bgSmsColor >> 2) & 0x03) * 85;
|
||||
int bgB = ((bgSmsColor >> 4) & 0x03) * 85;
|
||||
|
||||
// REPLACE THE R/G/B MATH WITH THIS SINGLE LINE:
|
||||
int bgAddress = (screenY * 256) + screenX;
|
||||
FrameBuffer[bgAddress] = (255 << 24) | (bgR << 16) | (bgG << 8) | bgB;
|
||||
FrameBuffer[bgAddress] = GetRgbColour(16, Registers[7] & 0x0F);
|
||||
|
||||
// Flag it as priority so sprites also hide behind the curtain!
|
||||
_priorityBuffer[bgAddress] = true;
|
||||
continue;
|
||||
}
|
||||
@@ -261,21 +258,17 @@ namespace Core.Video
|
||||
byte bp3 = VRAM[tileAddress + (readY * 4) + 3];
|
||||
|
||||
int readX = flipH ? tileX : (7 - tileX);
|
||||
int colorIndex = ((bp0 >> readX) & 1) | (((bp1 >> readX) & 1) << 1) |
|
||||
int colourIndex = ((bp0 >> readX) & 1) | (((bp1 >> readX) & 1) << 1) |
|
||||
(((bp2 >> readX) & 1) << 2) | (((bp3 >> readX) & 1) << 3);
|
||||
|
||||
int paletteOffset = useSpritePalette ? 16 : 0;
|
||||
byte smsColor = CRAM[paletteOffset + colorIndex];
|
||||
|
||||
int r = (smsColor & 0x03) * 85;
|
||||
int g = ((smsColor >> 2) & 0x03) * 85;
|
||||
int b = ((smsColor >> 4) & 0x03) * 85;
|
||||
int finalColour = GetRgbColour(paletteOffset, colourIndex);
|
||||
|
||||
int screenAddress = (screenY * 256) + screenX;
|
||||
|
||||
// Draw background and reset priority mask for this exact pixel
|
||||
FrameBuffer[screenAddress] = (255 << 24) | (r << 16) | (g << 8) | b;
|
||||
_priorityBuffer[screenAddress] = (priority && colorIndex != 0);
|
||||
FrameBuffer[screenAddress] = finalColour;
|
||||
_priorityBuffer[screenAddress] = (priority && colourIndex != 0);
|
||||
}
|
||||
|
||||
// --- 2. RENDER SPRITE LINE ---
|
||||
@@ -329,24 +322,53 @@ namespace Core.Video
|
||||
if (_priorityBuffer[(screenY * 256) + screenX]) continue;
|
||||
|
||||
int shift = 7 - px;
|
||||
int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
|
||||
int colourIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
|
||||
(((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3);
|
||||
|
||||
if (colorIndex == 0) continue;
|
||||
if (colourIndex == 0) continue;
|
||||
|
||||
byte smsColor = CRAM[16 + colorIndex];
|
||||
int r = (smsColor & 0x03) * 85;
|
||||
int g = ((smsColor >> 2) & 0x03) * 85;
|
||||
int b = ((smsColor >> 4) & 0x03) * 85;
|
||||
|
||||
FrameBuffer[(screenY * 256) + screenX] = (255 << 24) | (r << 16) | (g << 8) | b;
|
||||
// REPLACE THE R/G/B MATH WITH THIS SINGLE LINE:
|
||||
FrameBuffer[(screenY * 256) + screenX] = GetRgbColour(16, colourIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetRgbColour(int paletteOffset, int colourIndex)
|
||||
{
|
||||
//Debug.WriteLine(_isGameGear);
|
||||
int r, g, b;
|
||||
|
||||
if (IsGameGear)
|
||||
{
|
||||
// Game Gear: 2 bytes per colour. Format: ----BBBBGGGGRRRR
|
||||
int cramIndex = (paletteOffset + colourIndex) * 2;
|
||||
ushort ggcolour = (ushort)(ggCRAM[cramIndex] | (ggCRAM[cramIndex + 1] << 8));
|
||||
|
||||
// Extract 4-bit values (0-15) and map them to standard 8-bit values (0-255).
|
||||
// We multiply by 17 because 255 / 15 = 17!
|
||||
r = (ggcolour & 0x0F) * 17;
|
||||
g = ((ggcolour >> 4) & 0x0F) * 17;
|
||||
b = ((ggcolour >> 8) & 0x0F) * 17;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Master System: 1 byte per colour. Format: --BBGGRR
|
||||
byte smscolour = smsCRAM[paletteOffset + colourIndex];
|
||||
|
||||
// Extract 2-bit values (0-3) and map them to standard 8-bit values (0-255).
|
||||
// We multiply by 85 because 255 / 3 = 85!
|
||||
r = (smscolour & 0x03) * 85;
|
||||
g = ((smscolour >> 2) & 0x03) * 85;
|
||||
b = ((smscolour >> 4) & 0x03) * 85;
|
||||
}
|
||||
|
||||
return (255 << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
public void SaveState(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(VRAM);
|
||||
bw.Write(CRAM);
|
||||
bw.Write(smsCRAM);
|
||||
bw.Write(ggCRAM);
|
||||
bw.Write(Registers);
|
||||
bw.Write(_isSecondControlByte);
|
||||
bw.Write(_controlWord);
|
||||
@@ -359,12 +381,14 @@ namespace Core.Video
|
||||
// ADD THESE:
|
||||
bw.Write(_latchedHScroll);
|
||||
bw.Write(_latchedVScroll);
|
||||
bw.Write(IsGameGear);
|
||||
}
|
||||
|
||||
public void LoadState(BinaryReader br)
|
||||
{
|
||||
Array.Copy(br.ReadBytes(VRAM.Length), VRAM, VRAM.Length);
|
||||
Array.Copy(br.ReadBytes(CRAM.Length), CRAM, CRAM.Length);
|
||||
Array.Copy(br.ReadBytes(smsCRAM.Length), smsCRAM, smsCRAM.Length);
|
||||
Array.Copy(br.ReadBytes(ggCRAM.Length), ggCRAM, ggCRAM.Length);
|
||||
Array.Copy(br.ReadBytes(Registers.Length), Registers, Registers.Length);
|
||||
_isSecondControlByte = br.ReadBoolean();
|
||||
_controlWord = br.ReadUInt16();
|
||||
@@ -377,6 +401,7 @@ namespace Core.Video
|
||||
// ADD THESE:
|
||||
_latchedHScroll = br.ReadInt32();
|
||||
_latchedVScroll = br.ReadInt32();
|
||||
IsGameGear = br.ReadBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user