928 lines
36 KiB
C#
928 lines
36 KiB
C#
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Linq;
|
|||
|
using System.Text;
|
|||
|
using System.Threading.Tasks;
|
|||
|
using System.Diagnostics;
|
|||
|
|
|||
|
using Essgee.EventArguments;
|
|||
|
using Essgee.Utilities;
|
|||
|
|
|||
|
using static Essgee.Emulation.Utilities;
|
|||
|
|
|||
|
namespace Essgee.Emulation.Video
|
|||
|
{
|
|||
|
/* Sega 315-5124 (Mark III, SMS) and 315-5246 (SMS 2); differences see 'VDPDIFF' comments */
|
|||
|
public class SegaSMSVDP : TMS99xxA
|
|||
|
{
|
|||
|
/* VDPDIFF: switch for Mk3/SMS1 vs SMS2/GG behavior; configurable via SetRevision, maybe still split into separate classes instead? */
|
|||
|
protected VDPTypes vdpType = VDPTypes.Mk3SMS1;
|
|||
|
|
|||
|
public const int NumActiveScanlinesLow = 192;
|
|||
|
public const int NumActiveScanlinesMed = 224;
|
|||
|
public const int NumActiveScanlinesHigh = 240;
|
|||
|
|
|||
|
protected const int NumSpritesMode4 = 64;
|
|||
|
protected const int NumSpritesPerLineMode4 = 8;
|
|||
|
|
|||
|
public const int PortVCounter = 0x40; // 0x7E canonically, but mirrored across bus
|
|||
|
public const int PortHCounter = 0x41; // 0x7F canonically, but mirrored across bus
|
|||
|
|
|||
|
[StateRequired]
|
|||
|
protected byte[] cram;
|
|||
|
|
|||
|
[StateRequired]
|
|||
|
protected int vCounter, hCounter;
|
|||
|
protected int nametableHeight, vCounterTableIndex;
|
|||
|
[StateRequired]
|
|||
|
protected int lineInterruptCounter;
|
|||
|
protected int screenHeight;
|
|||
|
|
|||
|
bool isLineInterruptEnabled => IsBitSet(registers[0x00], 4);
|
|||
|
[StateRequired]
|
|||
|
bool isLineInterruptPending;
|
|||
|
|
|||
|
bool isColumn0MaskEnabled => IsBitSet(registers[0x00], 5);
|
|||
|
bool isVScrollPartiallyDisabled => IsBitSet(registers[0x00], 7); /* Columns 24-31, i.e. pixels 192-255 */
|
|||
|
bool isHScrollPartiallyDisabled => IsBitSet(registers[0x00], 6); /* Rows 0-1, i.e. pixels 0-15 */
|
|||
|
|
|||
|
bool isBitM4Set => IsBitSet(registers[0x00], 2);
|
|||
|
|
|||
|
protected override bool isModeGraphics1 => !(isBitM1Set || isBitM2Set || isBitM3Set || isBitM4Set);
|
|||
|
protected override bool isModeText => (isBitM1Set && !(isBitM2Set || isBitM3Set || isBitM4Set));
|
|||
|
protected override bool isModeGraphics2 => (isBitM2Set && !(isBitM1Set || isBitM3Set || isBitM4Set));
|
|||
|
protected override bool isModeMulticolor => (isBitM3Set && !(isBitM1Set || isBitM2Set || isBitM4Set));
|
|||
|
|
|||
|
protected bool isSMS240LineMode => (!isBitM1Set && isBitM2Set && isBitM3Set && isBitM4Set);
|
|||
|
protected bool isSMS224LineMode => (isBitM1Set && isBitM2Set && !isBitM3Set && isBitM4Set);
|
|||
|
|
|||
|
bool isSpriteShiftLeft8 => IsBitSet(registers[0x00], 3);
|
|||
|
|
|||
|
protected override ushort nametableBaseAddress
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (isBitM4Set)
|
|||
|
{
|
|||
|
if (isSMS224LineMode || isSMS240LineMode)
|
|||
|
return (ushort)(((registers[0x02] & 0x0C) << 10) | 0x700);
|
|||
|
else
|
|||
|
return (ushort)((registers[0x02] & 0x0E) << 10);
|
|||
|
}
|
|||
|
else
|
|||
|
return (ushort)((registers[0x02] & 0x0F) << 10);
|
|||
|
}
|
|||
|
}
|
|||
|
protected override ushort spriteAttribTableBaseAddress => (ushort)((registers[0x05] & (isBitM4Set ? 0x7E : 0x7F)) << 7);
|
|||
|
protected override ushort spritePatternGenBaseAddress => (ushort)((registers[0x06] & (isBitM4Set ? 0x04 : 0x07)) << 11);
|
|||
|
|
|||
|
/* http://www.smspower.org/Development/Palette */
|
|||
|
// TODO: verify these, SMSPower has some mistakes (RGB approx correct, palette value wrong)
|
|||
|
// (not that we'll really use this, aside from for F-16 Fighting Falcon, as SG1000 games should always be loaded into the SG1000 core...)
|
|||
|
readonly byte[] legacyColorMap = new byte[]
|
|||
|
{
|
|||
|
0x00, /* Transparent */
|
|||
|
0x00, /* Black */
|
|||
|
0x08, /* Medium green */
|
|||
|
0x0C, /* Light green */
|
|||
|
0x10, /* Dark blue */
|
|||
|
0x30, /* Light blue */
|
|||
|
0x01, /* Dark red */
|
|||
|
0x3C, /* Cyan */
|
|||
|
0x02, /* Medium red */
|
|||
|
0x03, /* Light red */
|
|||
|
0x05, /* Dark yellow */
|
|||
|
0x0F, /* Light yellow */
|
|||
|
0x04, /* Dark green */
|
|||
|
0x33, /* Magenta */
|
|||
|
0x15, /* Gray */
|
|||
|
0x3F /* White */
|
|||
|
};
|
|||
|
|
|||
|
readonly byte[][] vCounterTables = new byte[][]
|
|||
|
{
|
|||
|
/* NTSC, 192 lines */
|
|||
|
new byte[]
|
|||
|
{
|
|||
|
/* Top blanking */
|
|||
|
0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4,
|
|||
|
|
|||
|
/* Top border */
|
|||
|
0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4,
|
|||
|
0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
|||
|
|
|||
|
/* Active display */
|
|||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
|||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
|||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
|||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
|||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
|||
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|||
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
|||
|
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
|||
|
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
|
|||
|
|
|||
|
/* Bottom border */
|
|||
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
|
|||
|
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
|
|||
|
|
|||
|
/* Bottom blanking */
|
|||
|
0xD8, 0xD9, 0xDA,
|
|||
|
|
|||
|
/* Vertical blanking */
|
|||
|
0xD5, 0xD6, 0xD7
|
|||
|
},
|
|||
|
/* NTSC, 224 lines */
|
|||
|
new byte[]
|
|||
|
{
|
|||
|
/* Top blanking */
|
|||
|
0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4,
|
|||
|
|
|||
|
/* Top border */
|
|||
|
0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
|||
|
|
|||
|
/* Active display */
|
|||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
|||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
|||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
|||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
|||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
|||
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|||
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
|||
|
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
|||
|
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
|
|||
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
|
|||
|
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
|||
|
|
|||
|
/* Bottom border */
|
|||
|
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
|
|||
|
|
|||
|
/* Bottom blanking */
|
|||
|
0xE8, 0xE9, 0xEA,
|
|||
|
|
|||
|
/* Vertical blanking */
|
|||
|
0xE5, 0xE6, 0xE7,
|
|||
|
},
|
|||
|
/* NTSC, 240 lines (invalid) */
|
|||
|
new byte[]
|
|||
|
{
|
|||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
|||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
|||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
|||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
|||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
|||
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|||
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
|||
|
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
|||
|
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
|
|||
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
|
|||
|
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
|||
|
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
|||
|
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
|||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06
|
|||
|
},
|
|||
|
/* PAL, 192 lines */
|
|||
|
new byte[]
|
|||
|
{
|
|||
|
/* Top blanking */
|
|||
|
0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
|
|||
|
|
|||
|
/* Top border */
|
|||
|
0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9,
|
|||
|
0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9,
|
|||
|
0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9,
|
|||
|
0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
|||
|
|
|||
|
/* Active display */
|
|||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
|||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
|||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
|||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
|||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
|||
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|||
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
|||
|
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
|||
|
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
|
|||
|
|
|||
|
/* Bottom border */
|
|||
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
|
|||
|
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
|||
|
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
|||
|
|
|||
|
/* Bottom blanking */
|
|||
|
0xF0, 0xF1, 0xF2,
|
|||
|
|
|||
|
/* Vertical blanking */
|
|||
|
0xBA, 0xBB, 0xBC
|
|||
|
},
|
|||
|
/* PAL, 224 lines */
|
|||
|
new byte[]
|
|||
|
{
|
|||
|
/* Top blanking */
|
|||
|
0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9,
|
|||
|
|
|||
|
/* Top border */
|
|||
|
0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9,
|
|||
|
0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9,
|
|||
|
0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
|||
|
|
|||
|
/* Active display */
|
|||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
|||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
|||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
|||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
|||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
|||
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|||
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
|||
|
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
|||
|
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
|
|||
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
|
|||
|
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
|||
|
|
|||
|
/* Bottom border */
|
|||
|
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
|||
|
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
|||
|
|
|||
|
/* Bottom blanking */
|
|||
|
0x00, 0x01, 0x02,
|
|||
|
|
|||
|
/* Vertical blanking */
|
|||
|
0xCA, 0xCB, 0xCC,
|
|||
|
},
|
|||
|
/* PAL, 240 lines */
|
|||
|
new byte[]
|
|||
|
{
|
|||
|
/* Top blanking */
|
|||
|
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1,
|
|||
|
|
|||
|
/* Top border */
|
|||
|
0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1,
|
|||
|
0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
|||
|
|
|||
|
/* Active display */
|
|||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
|||
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
|||
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
|||
|
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
|||
|
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
|||
|
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
|||
|
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
|||
|
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
|||
|
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|||
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
|
|||
|
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
|
|||
|
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
|
|||
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
|
|||
|
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
|
|||
|
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
|
|||
|
|
|||
|
/* Bottom border */
|
|||
|
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
|
|||
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|||
|
|
|||
|
/* Bottom blanking */
|
|||
|
0x08, 0x09, 0x0A,
|
|||
|
|
|||
|
/* Vertical blanking */
|
|||
|
0xD2, 0xD3, 0xD4,
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
/* For H-counter emulation */
|
|||
|
readonly byte[] hCounterTable = new byte[]
|
|||
|
{
|
|||
|
0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0A, 0x0B, 0x0B,
|
|||
|
0x0C, 0x0D, 0x0E, 0x0E, 0x0F, 0x10, 0x11, 0x11, 0x12, 0x13, 0x14, 0x14, 0x15, 0x16, 0x17, 0x17,
|
|||
|
0x18, 0x19, 0x1A, 0x1A, 0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x20, 0x20, 0x21, 0x22, 0x23, 0x23,
|
|||
|
0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x29, 0x2A, 0x2B, 0x2C, 0x2C, 0x2D, 0x2E, 0x2F, 0x2F,
|
|||
|
0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x35, 0x35, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3A, 0x3B, 0x3B,
|
|||
|
0x3C, 0x3D, 0x3E, 0x3E, 0x3F, 0x40, 0x41, 0x41, 0x42, 0x43, 0x44, 0x44, 0x45, 0x46, 0x47, 0x47,
|
|||
|
0x48, 0x49, 0x4A, 0x4A, 0x4B, 0x4C, 0x4D, 0x4D, 0x4E, 0x4F, 0x50, 0x50, 0x51, 0x52, 0x53, 0x53,
|
|||
|
0x54, 0x55, 0x56, 0x56, 0x57, 0x58, 0x59, 0x59, 0x5A, 0x5B, 0x5C, 0x5C, 0x5D, 0x5E, 0x5F, 0x5F,
|
|||
|
0x60, 0x61, 0x62, 0x62, 0x63, 0x64, 0x65, 0x65, 0x66, 0x67, 0x68, 0x68, 0x69, 0x6A, 0x6B, 0x6B,
|
|||
|
0x6C, 0x6D, 0x6E, 0x6E, 0x6F, 0x70, 0x71, 0x71, 0x72, 0x73, 0x74, 0x74, 0x75, 0x76, 0x77, 0x77,
|
|||
|
0x78, 0x79, 0x7A, 0x7A, 0x7B, 0x7C, 0x7D, 0x7D, 0x7E, 0x7F, 0x80, 0x80, 0x81, 0x82, 0x83, 0x83,
|
|||
|
0x84, 0x85, 0x86, 0x86, 0x87, 0x88, 0x89, 0x89, 0x8A, 0x8B, 0x8C, 0x8C, 0x8D, 0x8E, 0x8F, 0x8F,
|
|||
|
0x90, 0x91, 0x92, 0x92, 0x93,
|
|||
|
|
|||
|
0xE9, 0xEA, 0xEA, 0xEB, 0xEC, 0xED, 0xED, 0xEE, 0xEF, 0xF0, 0xF0, 0xF1, 0xF2, 0xF3, 0xF3, 0xF4,
|
|||
|
0xF5, 0xF6, 0xF6, 0xF7, 0xF8, 0xF9, 0xF9, 0xFA, 0xFB, 0xFC, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF
|
|||
|
};
|
|||
|
|
|||
|
[StateRequired]
|
|||
|
byte horizontalScrollLatched, verticalScrollLatched;
|
|||
|
|
|||
|
const byte screenUsageBgLowPriority = screenUsageBackground;
|
|||
|
const byte screenUsageBgHighPriority = (1 << 2);
|
|||
|
|
|||
|
public int ScreenHeight => screenHeight;
|
|||
|
public int CurrentScanline => currentScanline;
|
|||
|
|
|||
|
public SegaSMSVDP() : base()
|
|||
|
{
|
|||
|
registers = new byte[0x0B];
|
|||
|
cram = new byte[0x20];
|
|||
|
|
|||
|
spriteBuffer = new (int Number, int Y, int X, int Pattern, int Attribute)[NumActiveScanlinesHigh][];
|
|||
|
for (int i = 0; i < spriteBuffer.Length; i++) spriteBuffer[i] = new (int Number, int Y, int X, int Pattern, int Attribute)[NumSpritesPerLineMode4];
|
|||
|
}
|
|||
|
|
|||
|
public override void Reset()
|
|||
|
{
|
|||
|
base.Reset();
|
|||
|
|
|||
|
WriteRegister(0x00, 0x36);
|
|||
|
WriteRegister(0x01, 0x80);
|
|||
|
WriteRegister(0x02, 0xFF);
|
|||
|
WriteRegister(0x03, 0xFF);
|
|||
|
WriteRegister(0x04, 0xFF);
|
|||
|
WriteRegister(0x05, 0xFF);
|
|||
|
WriteRegister(0x06, 0xFB);
|
|||
|
WriteRegister(0x07, 0x00);
|
|||
|
WriteRegister(0x08, 0x00);
|
|||
|
WriteRegister(0x09, 0x00);
|
|||
|
WriteRegister(0x0A, 0xFF);
|
|||
|
|
|||
|
for (int i = 0; i < cram.Length; i++) cram[i] = 0;
|
|||
|
|
|||
|
vCounter = hCounter = 0;
|
|||
|
lineInterruptCounter = registers[0x0A];
|
|||
|
|
|||
|
isLineInterruptPending = false;
|
|||
|
|
|||
|
horizontalScrollLatched = verticalScrollLatched = 0;
|
|||
|
|
|||
|
UpdateResolution();
|
|||
|
}
|
|||
|
|
|||
|
public override void SetRevision(int rev)
|
|||
|
{
|
|||
|
VDPTypes type = (VDPTypes)rev;
|
|||
|
Debug.Assert(Enum.IsDefined(typeof(VDPTypes), type), "Invalid revision", "{0} revision is invalid; only rev 0 (MK3/SMS1) or 1 (SMS2/GG) is valid", GetType().FullName);
|
|||
|
vdpType = type;
|
|||
|
}
|
|||
|
|
|||
|
protected override void ReconfigureTimings()
|
|||
|
{
|
|||
|
/* Calculate cycles/line */
|
|||
|
clockCyclesPerLine = (int)Math.Round((clockRate / refreshRate) / numTotalScanlines);
|
|||
|
|
|||
|
/* Create arrays */
|
|||
|
screenUsage = new byte[numVisiblePixels * numVisibleScanlines];
|
|||
|
outputFramebuffer = new byte[(numVisiblePixels * numVisibleScanlines) * 4];
|
|||
|
|
|||
|
/* Update resolution/display timing */
|
|||
|
UpdateResolution();
|
|||
|
}
|
|||
|
|
|||
|
public override void Step(int clockCyclesInStep)
|
|||
|
{
|
|||
|
InterruptLine = (((isFrameInterruptEnabled && isFrameInterruptPending) || (isLineInterruptEnabled && isLineInterruptPending)) ? InterruptState.Assert : InterruptState.Clear);
|
|||
|
|
|||
|
cycleCount += clockCyclesInStep;
|
|||
|
|
|||
|
hCounter = hCounterTable[(int)Math.Round((cycleCount + 578) / 3.0) % hCounterTable.Length];
|
|||
|
|
|||
|
if (cycleCount >= clockCyclesPerLine)
|
|||
|
{
|
|||
|
OnEndOfScanline(EventArgs.Empty);
|
|||
|
|
|||
|
horizontalScrollLatched = registers[0x08];
|
|||
|
|
|||
|
if (currentScanline == scanlineActiveDisplay)
|
|||
|
verticalScrollLatched = registers[0x09];
|
|||
|
|
|||
|
CheckSpriteOverflow(currentScanline);
|
|||
|
|
|||
|
RenderLine(currentScanline);
|
|||
|
|
|||
|
if (currentScanline >= scanlineActiveDisplay && currentScanline <= scanlineBottomBorder)
|
|||
|
{
|
|||
|
lineInterruptCounter--;
|
|||
|
if (lineInterruptCounter < 0)
|
|||
|
{
|
|||
|
lineInterruptCounter = registers[0x0A];
|
|||
|
isLineInterruptPending = true;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
lineInterruptCounter = registers[0x0A];
|
|||
|
|
|||
|
if (currentScanline == (scanlineBottomBorder + 1))
|
|||
|
isFrameInterruptPending = true;
|
|||
|
|
|||
|
vCounter = vCounterTables[vCounterTableIndex][currentScanline];
|
|||
|
|
|||
|
currentScanline++;
|
|||
|
if (currentScanline == numTotalScanlines)
|
|||
|
{
|
|||
|
currentScanline = 0;
|
|||
|
ClearScreenUsage();
|
|||
|
|
|||
|
PrepareRenderScreen();
|
|||
|
}
|
|||
|
|
|||
|
ParseSpriteTable(currentScanline);
|
|||
|
|
|||
|
cycleCount -= clockCyclesPerLine;
|
|||
|
if (cycleCount <= -clockCyclesPerLine) cycleCount = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override void PrepareRenderScreen()
|
|||
|
{
|
|||
|
OnRenderScreen(new RenderScreenEventArgs(numVisiblePixels, numVisibleScanlines, outputFramebuffer.Clone() as byte[]));
|
|||
|
}
|
|||
|
|
|||
|
protected override byte ReadVram(ushort address)
|
|||
|
{
|
|||
|
return vram[address & vramMask16k];
|
|||
|
}
|
|||
|
|
|||
|
protected override void WriteVram(ushort address, byte value)
|
|||
|
{
|
|||
|
vram[address & vramMask16k] = value;
|
|||
|
}
|
|||
|
|
|||
|
protected override void RenderLine(int y)
|
|||
|
{
|
|||
|
if (y >= scanlineTopBorder && y < scanlineActiveDisplay)
|
|||
|
{
|
|||
|
if (layerBordersForceEnable) SetLine(y, 1, backgroundColor);
|
|||
|
else SetLine(y, 0x00, 0x00, 0x00);
|
|||
|
}
|
|||
|
else if (y >= scanlineActiveDisplay && y < scanlineBottomBorder)
|
|||
|
{
|
|||
|
if (layerBackgroundForceEnable)
|
|||
|
{
|
|||
|
if (isBitM4Set)
|
|||
|
RenderLineMode4Background(y);
|
|||
|
else if (isModeGraphics1)
|
|||
|
RenderLineGraphics1Background(y);
|
|||
|
else if (isModeGraphics2)
|
|||
|
RenderLineGraphics2Background(y);
|
|||
|
else if (isModeMulticolor)
|
|||
|
RenderLineMulticolorBackground(y);
|
|||
|
else if (isModeText)
|
|||
|
RenderLineTextBackground(y);
|
|||
|
}
|
|||
|
else
|
|||
|
SetLine(y, 0x00, 0x00, 0x00);
|
|||
|
|
|||
|
if (layerSpritesForceEnable)
|
|||
|
{
|
|||
|
if (isBitM4Set)
|
|||
|
RenderLineMode4Sprites(y);
|
|||
|
else if (!isModeText && !isBitM4Set)
|
|||
|
RenderLineSprites(y);
|
|||
|
}
|
|||
|
|
|||
|
RenderBorders(y);
|
|||
|
}
|
|||
|
else if (y >= scanlineBottomBorder && y < numVisibleScanlines)
|
|||
|
{
|
|||
|
if (layerBordersForceEnable) SetLine(y, 1, backgroundColor);
|
|||
|
else SetLine(y, 0x00, 0x00, 0x00);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override void RenderBorders(int y)
|
|||
|
{
|
|||
|
for (int x = pixelLeftBorder; x < pixelActiveDisplay; x++)
|
|||
|
{
|
|||
|
if (layerBordersForceEnable) SetPixel(y, x, 1, backgroundColor);
|
|||
|
else SetPixel(y, x, 0x00, 0x00, 0x00);
|
|||
|
}
|
|||
|
for (int x = pixelRightBorder; x < numVisiblePixels; x++)
|
|||
|
{
|
|||
|
if (layerBordersForceEnable) SetPixel(y, x, 1, backgroundColor);
|
|||
|
else SetPixel(y, x, 0x00, 0x00, 0x00);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void SetLine(int y, int palette, int color)
|
|||
|
{
|
|||
|
for (int x = 0; x < numVisiblePixels; x++)
|
|||
|
SetPixel(y, x, palette, color);
|
|||
|
}
|
|||
|
|
|||
|
protected virtual void SetPixel(int y, int x, int palette, int color)
|
|||
|
{
|
|||
|
WriteColorToFramebuffer(palette, color, ((y * numVisiblePixels) + (x % numVisiblePixels)) * 4);
|
|||
|
}
|
|||
|
|
|||
|
private void RenderLineMode4Background(int y)
|
|||
|
{
|
|||
|
/* Determine coordinates in active display */
|
|||
|
int activeDisplayY = (y - scanlineActiveDisplay);
|
|||
|
|
|||
|
/* Determine H scrolling parameters */
|
|||
|
int currentHorizontalScroll = ((isHScrollPartiallyDisabled && activeDisplayY < 16) ? 0 : horizontalScrollLatched);
|
|||
|
int horizontalScrollCoarse = (currentHorizontalScroll >> 3);
|
|||
|
int horizontalScrollFine = (currentHorizontalScroll & 0x07);
|
|||
|
|
|||
|
ushort currentNametableBaseAddress = nametableBaseAddress;
|
|||
|
bool currentIsVScrollPartiallyDisabled = isVScrollPartiallyDisabled;
|
|||
|
bool currentIsColumn0MaskEnabled = isColumn0MaskEnabled;
|
|||
|
|
|||
|
for (int x = 0; x < numTotalPixelsPerScanline; x++)
|
|||
|
{
|
|||
|
int activeDisplayX = (x - pixelActiveDisplay);
|
|||
|
if (activeDisplayX < 0 || activeDisplayX >= NumActivePixelsPerScanline) continue;
|
|||
|
|
|||
|
/* Determine V scrolling parameters */
|
|||
|
int currentVerticalScroll = ((currentIsVScrollPartiallyDisabled && activeDisplayX >= 192) ? 0 : verticalScrollLatched);
|
|||
|
int verticalScrollCoarse = (currentVerticalScroll >> 3);
|
|||
|
int verticalScrollFine = (currentVerticalScroll & 0x07);
|
|||
|
|
|||
|
/* Calculate current scrolled column and row */
|
|||
|
int numColumns = 32;
|
|||
|
int currentColumn = (((activeDisplayX - horizontalScrollFine) / 8) - horizontalScrollCoarse) & (numColumns - 1);
|
|||
|
int currentRow = (((activeDisplayY + verticalScrollFine) / 8) + verticalScrollCoarse) % (nametableHeight / 8);
|
|||
|
|
|||
|
/* VDPDIFF: Mk3/SMS1 VDP only, adjust current row according to mask bit; http://www.smspower.org/Development/TilemapMirroring
|
|||
|
* NOTE: Emulating this breaks 224/240-line mode games (ex. Fantastic Dizzy, Cosmic Spacehead, Micro Machines)?
|
|||
|
*/
|
|||
|
if (vdpType == VDPTypes.Mk3SMS1)
|
|||
|
currentRow &= (((registers[0x02] & 0x01) << 4) | 0x0F);
|
|||
|
|
|||
|
/* Fetch data from nametable & extract properties */
|
|||
|
ushort nametableAddress = (ushort)(currentNametableBaseAddress + (currentRow * (numColumns * 2)) + (currentColumn * 2));
|
|||
|
ushort nametableData = (ushort)(ReadVram((ushort)(nametableAddress + 1)) << 8 | ReadVram(nametableAddress));
|
|||
|
|
|||
|
int tileIndex = (nametableData & 0x01FF);
|
|||
|
bool hFlip = ((nametableData & 0x0200) == 0x0200);
|
|||
|
bool vFlip = ((nametableData & 0x0400) == 0x0400);
|
|||
|
int palette = (((nametableData & 0x0800) >> 11) & 0x0001);
|
|||
|
bool priority = ((nametableData & 0x1000) == 0x1000);
|
|||
|
|
|||
|
/* Fetch pixel data for current pixel line */
|
|||
|
int hPixel = ((activeDisplayX - horizontalScrollFine) % 8);
|
|||
|
hPixel = (hFlip ? hPixel : (7 - hPixel));
|
|||
|
int vPixel = ((activeDisplayY + verticalScrollFine) % 8);
|
|||
|
vPixel = (!vFlip ? vPixel : (7 - vPixel));
|
|||
|
|
|||
|
ushort tileAddress = (ushort)((tileIndex << 5) + (vPixel << 2));
|
|||
|
int c = (((ReadVram((ushort)(tileAddress + 0)) >> hPixel) & 0x01) << 0);
|
|||
|
c |= (((ReadVram((ushort)(tileAddress + 1)) >> hPixel) & 0x01) << 1);
|
|||
|
c |= (((ReadVram((ushort)(tileAddress + 2)) >> hPixel) & 0x01) << 2);
|
|||
|
c |= (((ReadVram((ushort)(tileAddress + 3)) >> hPixel) & 0x01) << 3);
|
|||
|
|
|||
|
/* Record screen usage, write to framebuffer */
|
|||
|
if (GetScreenUsageFlag(y, x) == screenUsageEmpty)
|
|||
|
{
|
|||
|
if ((currentIsColumn0MaskEnabled && (activeDisplayX / 8) == 0) || isDisplayBlanked)
|
|||
|
SetPixel(y, x, 1, backgroundColor);
|
|||
|
else
|
|||
|
SetPixel(y, x, palette, c);
|
|||
|
|
|||
|
SetScreenUsageFlag(y, x, (c != 0 && priority) ? screenUsageBgHighPriority : screenUsageBgLowPriority);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override void CheckSpriteOverflow(int y)
|
|||
|
{
|
|||
|
if (!isBitM4Set)
|
|||
|
{
|
|||
|
/* Not in Master System video mode */
|
|||
|
base.CheckSpriteOverflow(y);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Ensure current scanline is within active display */
|
|||
|
if (y >= scanlineActiveDisplay && y < scanlineBottomBorder)
|
|||
|
{
|
|||
|
int activeDisplayY = (y - scanlineActiveDisplay);
|
|||
|
|
|||
|
/* If last sprite in buffer is valid, sprite overflow occured */
|
|||
|
int lastSpriteInBuffer = spriteBuffer[activeDisplayY][NumSpritesPerLineMode4 - 1].Number;
|
|||
|
if (lastSpriteInBuffer != -1)
|
|||
|
{
|
|||
|
isSpriteOverflow = true;
|
|||
|
|
|||
|
/* Store sprite number in status register */
|
|||
|
/* NOTE: the last illegal sprite is *technically* only stored here in TMS99xxA modes, but still emulating it in Mode 4 should be fine */
|
|||
|
WriteSpriteNumberToStatus(lastSpriteInBuffer);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected override void ParseSpriteTable(int y)
|
|||
|
{
|
|||
|
if (!isBitM4Set)
|
|||
|
{
|
|||
|
/* Not in Master System video mode */
|
|||
|
base.ParseSpriteTable(y);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (y < scanlineActiveDisplay || y >= scanlineBottomBorder) return;
|
|||
|
|
|||
|
/* Determine coordinates in active display */
|
|||
|
int activeDisplayY = (y - scanlineActiveDisplay);
|
|||
|
|
|||
|
/* Clear sprite list for current line */
|
|||
|
for (int i = 0; i < spriteBuffer[activeDisplayY].Length; i++) spriteBuffer[activeDisplayY][i] = (-1, 0, 0, 0, 0);
|
|||
|
|
|||
|
/* Determine sprite size & get zoomed sprites adjustment */
|
|||
|
int zoomShift = (isZoomedSprites ? 1 : 0);
|
|||
|
int spriteHeight = ((isLargeSprites ? 16 : 8) << zoomShift);
|
|||
|
|
|||
|
int numValidSprites = 0;
|
|||
|
for (int sprite = 0; sprite < NumSpritesMode4; sprite++)
|
|||
|
{
|
|||
|
int yCoordinate = ReadVram((ushort)(spriteAttribTableBaseAddress + sprite));
|
|||
|
|
|||
|
/* Ignore following if Y coord is 208 in 192-line mode */
|
|||
|
if (yCoordinate == 208 && screenHeight == NumActiveScanlinesLow)
|
|||
|
{
|
|||
|
/* Store first "illegal sprite" number in status register */
|
|||
|
WriteSpriteNumberToStatus(sprite);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/* Modify Y coord as needed */
|
|||
|
yCoordinate++;
|
|||
|
if (yCoordinate > screenHeight + 32) yCoordinate -= 256;
|
|||
|
|
|||
|
/* Ignore this sprite if on incorrect lines */
|
|||
|
if (activeDisplayY < yCoordinate || activeDisplayY >= (yCoordinate + spriteHeight)) continue;
|
|||
|
|
|||
|
/* Check if maximum number of sprites per line is reached */
|
|||
|
numValidSprites++;
|
|||
|
if (numValidSprites > NumSpritesPerLineMode4) return;
|
|||
|
|
|||
|
/* Mark sprite for rendering */
|
|||
|
int xCoordinate = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x80 + (sprite * 2)));
|
|||
|
int patternNumber = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x80 + (sprite * 2) + 1));
|
|||
|
int unusedData = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x40 + (sprite * 2)));
|
|||
|
|
|||
|
spriteBuffer[activeDisplayY][numValidSprites - 1] = (sprite, yCoordinate, xCoordinate, patternNumber, unusedData);
|
|||
|
}
|
|||
|
|
|||
|
/* Because we didn't bow out before already, store total number of sprites in status register */
|
|||
|
WriteSpriteNumberToStatus(NumSprites - 1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void RenderLineMode4Sprites(int y)
|
|||
|
{
|
|||
|
if (y < scanlineActiveDisplay || y >= scanlineBottomBorder) return;
|
|||
|
|
|||
|
/* Determine coordinates in active display */
|
|||
|
int activeDisplayY = (y - scanlineActiveDisplay);
|
|||
|
|
|||
|
/* Determine sprite size & get zoomed sprites adjustment */
|
|||
|
int zoomShift = (isZoomedSprites ? 1 : 0);
|
|||
|
int spriteHeight = ((isLargeSprites ? 16 : 8) << zoomShift);
|
|||
|
|
|||
|
for (int s = 0; s < spriteBuffer[activeDisplayY].Length; s++)
|
|||
|
{
|
|||
|
var sprite = spriteBuffer[activeDisplayY][s];
|
|||
|
|
|||
|
if (sprite.Number == -1) continue;
|
|||
|
|
|||
|
/* VDPDIFF: Mk3/SMS1 VDP zoomed sprites bug, only first four sprites (same as max sprites/line on TMS99xxA) can be zoomed horizontally
|
|||
|
* Zoom works normally on SMS2/GG */
|
|||
|
int spriteWidth = (8 << zoomShift);
|
|||
|
if (vdpType == VDPTypes.Mk3SMS1 && s >= NumSpritesPerLine) spriteWidth = 8;
|
|||
|
|
|||
|
if (!isDisplayBlanked)
|
|||
|
{
|
|||
|
int yCoordinate = sprite.Y;
|
|||
|
int xCoordinate = sprite.X;
|
|||
|
int patternNumber = sprite.Pattern;
|
|||
|
int unusedData = sprite.Attribute;
|
|||
|
|
|||
|
if (isSpriteShiftLeft8) xCoordinate -= 8;
|
|||
|
|
|||
|
for (int pixel = 0; pixel < spriteWidth; pixel++)
|
|||
|
{
|
|||
|
/* Ignore pixel if column 0 masking is enabled and sprite pixel is in column 0 */
|
|||
|
if (isColumn0MaskEnabled && (xCoordinate + pixel) < 8) continue;
|
|||
|
|
|||
|
/* Check if sprite is outside active display, else continue to next sprite */
|
|||
|
if ((xCoordinate + pixel) < 0 || (xCoordinate + pixel) >= NumActivePixelsPerScanline) continue;
|
|||
|
|
|||
|
/* Determine coordinate inside sprite */
|
|||
|
int inSpriteXCoord = (pixel >> zoomShift) % spriteWidth;
|
|||
|
int inSpriteYCoord = ((activeDisplayY - yCoordinate) >> zoomShift) % spriteHeight;
|
|||
|
|
|||
|
/* Calculate address and fetch pixel data */
|
|||
|
int tileIndex = patternNumber;
|
|||
|
if (isLargeSprites) tileIndex &= ~0x01;
|
|||
|
ushort tileAddress = (ushort)(spritePatternGenBaseAddress + (tileIndex << 5) + (inSpriteYCoord << 2));
|
|||
|
|
|||
|
/* Get color & check transparency and position */
|
|||
|
int c = (((ReadVram((ushort)(tileAddress + 0)) >> (7 - inSpriteXCoord)) & 0x1) << 0);
|
|||
|
c |= (((ReadVram((ushort)(tileAddress + 1)) >> (7 - inSpriteXCoord)) & 0x1) << 1);
|
|||
|
c |= (((ReadVram((ushort)(tileAddress + 2)) >> (7 - inSpriteXCoord)) & 0x1) << 2);
|
|||
|
c |= (((ReadVram((ushort)(tileAddress + 3)) >> (7 - inSpriteXCoord)) & 0x1) << 3);
|
|||
|
|
|||
|
if (c == 0) continue;
|
|||
|
|
|||
|
int x = pixelActiveDisplay + (xCoordinate + pixel);
|
|||
|
if (IsScreenUsageFlagSet(y, x, screenUsageSprite))
|
|||
|
{
|
|||
|
/* If sprite was already at this location, set sprite collision flag */
|
|||
|
isSpriteCollision = true;
|
|||
|
}
|
|||
|
else if (!IsScreenUsageFlagSet(y, x, screenUsageBgHighPriority))
|
|||
|
{
|
|||
|
/* Draw if pixel isn't occupied by high-priority BG */
|
|||
|
SetPixel(y, x, 1, c);
|
|||
|
}
|
|||
|
|
|||
|
/* Note that there is a sprite here regardless */
|
|||
|
|
|||
|
/* VDPDIFF: Mk3 / SMS1 VDP zoomed sprites bug, horizontally zoomed area of sprite (i.e. pixels 9-16) is ignored by collision; do not mark location as containing a sprite
|
|||
|
* https://www.smspower.org/forums/post109677#109677
|
|||
|
* TODO: verify behavior somehow?
|
|||
|
*/
|
|||
|
if ((vdpType == VDPTypes.Mk3SMS1 && isZoomedSprites && pixel < 8) || (vdpType == VDPTypes.Mk3SMS1 && !isZoomedSprites) || vdpType == VDPTypes.SMS2GG)
|
|||
|
SetScreenUsageFlag(y, x, screenUsageSprite);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
protected void UpdateResolution()
|
|||
|
{
|
|||
|
/* Check screenmode */
|
|||
|
if (isSMS240LineMode)
|
|||
|
{
|
|||
|
screenHeight = NumActiveScanlinesHigh;
|
|||
|
nametableHeight = 256;
|
|||
|
vCounterTableIndex = (isPalChip ? 5 : 2);
|
|||
|
}
|
|||
|
else if (isSMS224LineMode)
|
|||
|
{
|
|||
|
screenHeight = NumActiveScanlinesMed;
|
|||
|
nametableHeight = 256;
|
|||
|
vCounterTableIndex = (isPalChip ? 4 : 1);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
screenHeight = NumActiveScanlinesLow;
|
|||
|
nametableHeight = 224;
|
|||
|
vCounterTableIndex = (isPalChip ? 3 : 0);
|
|||
|
}
|
|||
|
|
|||
|
/* Scanline parameters */
|
|||
|
if (!isPalChip)
|
|||
|
{
|
|||
|
/* NTSC */
|
|||
|
if (screenHeight == NumActiveScanlinesHigh)
|
|||
|
{
|
|||
|
/* 240 active lines, invalid on NTSC (dummy values); "Line Interrupt Test #1" mode 1 will show blue screen */
|
|||
|
topBorderSize = 0;
|
|||
|
verticalActiveDisplaySize = 0;
|
|||
|
bottomBorderSize = 0;
|
|||
|
}
|
|||
|
else if (screenHeight == NumActiveScanlinesMed)
|
|||
|
{
|
|||
|
/* 224 active lines */
|
|||
|
topBorderSize = 11;
|
|||
|
verticalActiveDisplaySize = 224;
|
|||
|
bottomBorderSize = 8;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* 192 active lines */
|
|||
|
topBorderSize = 27;
|
|||
|
verticalActiveDisplaySize = 192;
|
|||
|
bottomBorderSize = 24;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* PAL */
|
|||
|
if (screenHeight == NumActiveScanlinesHigh)
|
|||
|
{
|
|||
|
/* 240 active lines */
|
|||
|
topBorderSize = 30;
|
|||
|
verticalActiveDisplaySize = 240;
|
|||
|
bottomBorderSize = 24;
|
|||
|
}
|
|||
|
else if (screenHeight == NumActiveScanlinesMed)
|
|||
|
{
|
|||
|
/* 224 active lines */
|
|||
|
topBorderSize = 38;
|
|||
|
verticalActiveDisplaySize = 224;
|
|||
|
bottomBorderSize = 32;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* 192 active lines */
|
|||
|
topBorderSize = 54;
|
|||
|
verticalActiveDisplaySize = 192;
|
|||
|
bottomBorderSize = 48;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
scanlineTopBorder = 0;
|
|||
|
scanlineActiveDisplay = (scanlineTopBorder + topBorderSize);
|
|||
|
scanlineBottomBorder = (scanlineActiveDisplay + verticalActiveDisplaySize);
|
|||
|
|
|||
|
numVisibleScanlines = (topBorderSize + verticalActiveDisplaySize + bottomBorderSize);
|
|||
|
|
|||
|
/* Pixel parameters */
|
|||
|
leftBorderSize = 13;
|
|||
|
horizontalActiveDisplaySize = 256;
|
|||
|
rightBorderSize = 15;
|
|||
|
|
|||
|
pixelLeftBorder = 0;
|
|||
|
pixelActiveDisplay = (pixelLeftBorder + leftBorderSize);
|
|||
|
pixelRightBorder = (pixelActiveDisplay + horizontalActiveDisplaySize);
|
|||
|
|
|||
|
numVisiblePixels = (leftBorderSize + horizontalActiveDisplaySize + rightBorderSize);
|
|||
|
}
|
|||
|
|
|||
|
protected virtual void WriteColorToFramebuffer(int palette, int color, int address)
|
|||
|
{
|
|||
|
WriteColorToFramebuffer(cram[((palette * 16) + color)], address);
|
|||
|
}
|
|||
|
|
|||
|
protected override void WriteColorToFramebuffer(ushort colorValue, int address)
|
|||
|
{
|
|||
|
/* If not in Master System video mode, color value is index into legacy colormap */
|
|||
|
if (!isBitM4Set)
|
|||
|
colorValue = (legacyColorMap[colorValue & 0x000F]);
|
|||
|
|
|||
|
RGB222toBGRA8888(colorValue, ref outputFramebuffer, address);
|
|||
|
}
|
|||
|
|
|||
|
protected override void WriteDataPort(byte value)
|
|||
|
{
|
|||
|
isSecondControlWrite = false;
|
|||
|
|
|||
|
readBuffer = value;
|
|||
|
|
|||
|
switch (codeRegister)
|
|||
|
{
|
|||
|
case 0x00:
|
|||
|
case 0x01:
|
|||
|
case 0x02:
|
|||
|
WriteVram(addressRegister, value);
|
|||
|
break;
|
|||
|
case 0x03:
|
|||
|
cram[(addressRegister & 0x001F)] = value;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
addressRegister++;
|
|||
|
}
|
|||
|
|
|||
|
protected override byte ReadControlPort()
|
|||
|
{
|
|||
|
byte statusCurrent = (byte)statusFlags;
|
|||
|
|
|||
|
statusFlags = StatusFlags.None;
|
|||
|
isSecondControlWrite = false;
|
|||
|
|
|||
|
isLineInterruptPending = false;
|
|||
|
|
|||
|
InterruptLine = InterruptState.Clear;
|
|||
|
|
|||
|
return statusCurrent;
|
|||
|
}
|
|||
|
|
|||
|
public override byte ReadPort(byte port)
|
|||
|
{
|
|||
|
if ((port & 0x40) == 0x40)
|
|||
|
if ((port & 0x01) == 0)
|
|||
|
return (byte)vCounter; /* V counter */
|
|||
|
else
|
|||
|
return (byte)hCounter; /* H counter */
|
|||
|
else
|
|||
|
return base.ReadPort(port);
|
|||
|
}
|
|||
|
|
|||
|
protected override void WriteRegister(byte register, byte value)
|
|||
|
{
|
|||
|
if (register < registers.Length)
|
|||
|
registers[register] = value;
|
|||
|
|
|||
|
if (register == 0x00 || register == 0x01)
|
|||
|
UpdateResolution();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|