529 lines
15 KiB
C#
529 lines
15 KiB
C#
using System;
|
|
using System.IO;
|
|
|
|
using BizHawk.Common;
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Emulation.Cores.Components.H6280;
|
|
|
|
namespace BizHawk.Emulation.Cores.PCEngine
|
|
{
|
|
// ------------------------------------------------------
|
|
// HuC6202 Video Priority Controller
|
|
// ------------------------------------------------------
|
|
// Responsible for merging VDC1 and VDC2 data on the SuperGrafx.
|
|
// Pretty much all documentation on the SuperGrafx courtesy of Charles MacDonald.
|
|
|
|
public sealed class VPC : IVideoProvider
|
|
{
|
|
PCEngine PCE;
|
|
public VDC VDC1;
|
|
public VDC VDC2;
|
|
public VCE VCE;
|
|
public HuC6280 CPU;
|
|
|
|
public byte[] Registers = { 0x11, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
public int Window1Width { get { return ((Registers[3] & 3) << 8) | Registers[2]; } }
|
|
public int Window2Width { get { return ((Registers[5] & 3) << 8) | Registers[4]; } }
|
|
public int PriorityModeSlot0 { get { return Registers[0] & 0x0F; } }
|
|
public int PriorityModeSlot1 { get { return (Registers[0] >> 4) & 0x0F; } }
|
|
public int PriorityModeSlot2 { get { return Registers[1] & 0x0F; } }
|
|
public int PriorityModeSlot3 { get { return (Registers[1] >> 4) & 0x0F; } }
|
|
|
|
public VPC(PCEngine pce, VDC vdc1, VDC vdc2, VCE vce, HuC6280 cpu)
|
|
{
|
|
PCE = pce;
|
|
VDC1 = vdc1;
|
|
VDC2 = vdc2;
|
|
VCE = vce;
|
|
CPU = cpu;
|
|
|
|
// latch initial video buffer
|
|
FrameBuffer = vdc1.GetVideoBuffer();
|
|
FrameWidth = vdc1.BufferWidth;
|
|
FrameHeight = vdc1.BufferHeight;
|
|
}
|
|
|
|
public byte ReadVPC(int port)
|
|
{
|
|
port &= 0x0F;
|
|
switch (port)
|
|
{
|
|
case 0x08: return Registers[0];
|
|
case 0x09: return Registers[1];
|
|
case 0x0A: return Registers[2];
|
|
case 0x0B: return Registers[3];
|
|
case 0x0C: return Registers[4];
|
|
case 0x0D: return Registers[5];
|
|
case 0x0E: return Registers[6];
|
|
case 0x0F: return 0;
|
|
default: return 0xFF;
|
|
}
|
|
}
|
|
|
|
public void WriteVPC(int port, byte value)
|
|
{
|
|
port &= 0x0F;
|
|
switch (port)
|
|
{
|
|
case 0x08: Registers[0] = value; break;
|
|
case 0x09: Registers[1] = value; break;
|
|
case 0x0A: Registers[2] = value; break;
|
|
case 0x0B: Registers[3] = value; break;
|
|
case 0x0C: Registers[4] = value; break;
|
|
case 0x0D: Registers[5] = value; break;
|
|
case 0x0E:
|
|
// CPU Store Immediate VDC Select
|
|
CPU.WriteVDC = (value & 1) == 0 ? (Action<int, byte>)VDC1.WriteVDC : VDC2.WriteVDC;
|
|
Registers[6] = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.BeginSection("VPC");
|
|
ser.Sync("Registers", ref Registers, false);
|
|
ser.EndSection();
|
|
|
|
if (ser.IsReader)
|
|
WriteVPC(0x0E, Registers[6]);
|
|
}
|
|
|
|
// We use a single priority mode for the whole frame.
|
|
// No commercial SGX games really use the 'window' features AFAIK.
|
|
// And there are no homebrew SGX games I know of.
|
|
// Maybe we'll emulate it in the native-code version.
|
|
|
|
const int RCR = 6;
|
|
const int BXR = 7;
|
|
const int BYR = 8;
|
|
const int VDW = 13;
|
|
const int DCR = 15;
|
|
|
|
int EffectivePriorityMode = 0;
|
|
|
|
int FrameHeight;
|
|
int FrameWidth;
|
|
int[] FrameBuffer;
|
|
|
|
byte[] PriorityBuffer = new byte[512];
|
|
byte[] InterSpritePriorityBuffer = new byte[512];
|
|
|
|
public void ExecFrame(bool render)
|
|
{
|
|
// Determine the effective priority mode.
|
|
if (Window1Width < 0x40 && Window2Width < 0x40)
|
|
EffectivePriorityMode = PriorityModeSlot3 >> 2;
|
|
else if (Window2Width > 512)
|
|
EffectivePriorityMode = PriorityModeSlot1 >> 2;
|
|
else
|
|
{
|
|
Console.WriteLine("Unsupported VPC window settings");
|
|
EffectivePriorityMode = 0;
|
|
}
|
|
|
|
// Latch frame dimensions and framebuffer, for purely dumb reasons
|
|
FrameWidth = VDC1.BufferWidth;
|
|
FrameHeight = VDC1.BufferHeight;
|
|
FrameBuffer = VDC1.GetVideoBuffer();
|
|
|
|
int ScanLine = 0;
|
|
while (true)
|
|
{
|
|
VDC1.ScanLine = ScanLine;
|
|
VDC2.ScanLine = ScanLine;
|
|
|
|
int ActiveDisplayStartLine = VDC1.DisplayStartLine;
|
|
int VBlankLine = ActiveDisplayStartLine + VDC1.Registers[VDW] + 1;
|
|
if (VBlankLine > 261)
|
|
VBlankLine = 261;
|
|
VDC1.ActiveLine = ScanLine - ActiveDisplayStartLine;
|
|
VDC2.ActiveLine = VDC1.ActiveLine;
|
|
bool InActiveDisplay = (ScanLine >= ActiveDisplayStartLine) && (ScanLine < VBlankLine);
|
|
|
|
if (ScanLine == ActiveDisplayStartLine)
|
|
{
|
|
VDC1.RCRCounter = 0x40;
|
|
VDC2.RCRCounter = 0x40;
|
|
}
|
|
|
|
if (ScanLine == VBlankLine)
|
|
{
|
|
VDC1.UpdateSpriteAttributeTable();
|
|
VDC2.UpdateSpriteAttributeTable();
|
|
}
|
|
|
|
if (VDC1.RCRCounter == (VDC1.Registers[RCR] & 0x3FF))
|
|
{
|
|
if (VDC1.RasterCompareInterruptEnabled)
|
|
{
|
|
VDC1.StatusByte |= VDC.StatusRasterCompare;
|
|
CPU.IRQ1Assert = true;
|
|
}
|
|
}
|
|
|
|
if (VDC2.RCRCounter == (VDC2.Registers[RCR] & 0x3FF))
|
|
{
|
|
if (VDC2.RasterCompareInterruptEnabled)
|
|
{
|
|
VDC2.StatusByte |= VDC.StatusRasterCompare;
|
|
CPU.IRQ1Assert = true;
|
|
}
|
|
}
|
|
|
|
CPU.Execute(VDC1.HBlankCycles);
|
|
|
|
if (InActiveDisplay)
|
|
{
|
|
if (ScanLine == ActiveDisplayStartLine)
|
|
{
|
|
VDC1.BackgroundY = VDC1.Registers[BYR];
|
|
VDC2.BackgroundY = VDC2.Registers[BYR];
|
|
}
|
|
else
|
|
{
|
|
VDC1.BackgroundY++;
|
|
VDC1.BackgroundY &= 0x01FF;
|
|
VDC2.BackgroundY++;
|
|
VDC2.BackgroundY &= 0x01FF;
|
|
}
|
|
if (render) RenderScanLine();
|
|
}
|
|
|
|
if (ScanLine == VBlankLine && VDC1.VBlankInterruptEnabled)
|
|
VDC1.StatusByte |= VDC.StatusVerticalBlanking;
|
|
|
|
if (ScanLine == VBlankLine && VDC2.VBlankInterruptEnabled)
|
|
VDC2.StatusByte |= VDC.StatusVerticalBlanking;
|
|
|
|
if (ScanLine == VBlankLine + 4 && VDC1.SatDmaPerformed)
|
|
{
|
|
VDC1.SatDmaPerformed = false;
|
|
if ((VDC1.Registers[DCR] & 1) > 0)
|
|
VDC1.StatusByte |= VDC.StatusVramSatDmaComplete;
|
|
}
|
|
|
|
if (ScanLine == VBlankLine + 4 && VDC2.SatDmaPerformed)
|
|
{
|
|
VDC2.SatDmaPerformed = false;
|
|
if ((VDC2.Registers[DCR] & 1) > 0)
|
|
VDC2.StatusByte |= VDC.StatusVramSatDmaComplete;
|
|
}
|
|
|
|
CPU.Execute(2);
|
|
|
|
if ((VDC1.StatusByte & (VDC.StatusVerticalBlanking | VDC.StatusVramSatDmaComplete)) != 0)
|
|
CPU.IRQ1Assert = true;
|
|
|
|
if ((VDC2.StatusByte & (VDC.StatusVerticalBlanking | VDC.StatusVramSatDmaComplete)) != 0)
|
|
CPU.IRQ1Assert = true;
|
|
|
|
CPU.Execute(455 - VDC1.HBlankCycles - 2);
|
|
|
|
if (InActiveDisplay == false && VDC1.DmaRequested)
|
|
VDC1.RunDmaForScanline();
|
|
|
|
if (InActiveDisplay == false && VDC2.DmaRequested)
|
|
VDC2.RunDmaForScanline();
|
|
|
|
VDC1.RCRCounter++;
|
|
VDC2.RCRCounter++;
|
|
ScanLine++;
|
|
|
|
if (ScanLine == VCE.NumberOfScanlines)
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RenderScanLine()
|
|
{
|
|
if (VDC1.ActiveLine >= FrameHeight)
|
|
return;
|
|
|
|
InitializeScanLine(VDC1.ActiveLine);
|
|
|
|
switch (EffectivePriorityMode)
|
|
{
|
|
case 0:
|
|
RenderBackgroundScanline(VDC1, 12, PCE.Settings.ShowBG1);
|
|
RenderBackgroundScanline(VDC2, 2, PCE.Settings.ShowBG2);
|
|
RenderSpritesScanline(VDC1, 11, 14, PCE.Settings.ShowOBJ1);
|
|
RenderSpritesScanline(VDC2, 1, 3, PCE.Settings.ShowOBJ2);
|
|
break;
|
|
case 1:
|
|
RenderBackgroundScanline(VDC1, 12, PCE.Settings.ShowBG1);
|
|
RenderBackgroundScanline(VDC2, 2, PCE.Settings.ShowBG2);
|
|
RenderSpritesScanline(VDC1, 11, 14, PCE.Settings.ShowOBJ1);
|
|
RenderSpritesScanline(VDC2, 1, 13, PCE.Settings.ShowOBJ2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void InitializeScanLine(int scanline)
|
|
{
|
|
// Clear priority buffer
|
|
Array.Clear(PriorityBuffer, 0, FrameWidth);
|
|
|
|
// Initialize scanline to background color
|
|
for (int i = 0; i < FrameWidth; i++)
|
|
FrameBuffer[(scanline * FrameWidth) + i] = VCE.Palette[256];
|
|
}
|
|
|
|
unsafe void RenderBackgroundScanline(VDC vdc, byte priority, bool show)
|
|
{
|
|
if (vdc.BackgroundEnabled == false)
|
|
return;
|
|
|
|
// per-line parameters
|
|
int vertLine = vdc.BackgroundY;
|
|
vertLine %= vdc.BatHeight * 8;
|
|
int yTile = (vertLine / 8);
|
|
int yOfs = vertLine % 8;
|
|
int xScroll = vdc.Registers[BXR] & 0x3FF;
|
|
int BatRowMask = vdc.BatWidth - 1;
|
|
|
|
fixed (ushort* VRAMptr = vdc.VRAM)
|
|
fixed (int* PALptr = VCE.Palette)
|
|
fixed (byte* Patternptr = vdc.PatternBuffer)
|
|
fixed (int* FBptr = FrameBuffer)
|
|
fixed (byte* Priortyptr = PriorityBuffer)
|
|
{
|
|
// pointer to the BAT and the framebuffer for this line
|
|
ushort* BatRow = VRAMptr + yTile * vdc.BatWidth;
|
|
int* dst = FBptr + vdc.ActiveLine * FrameWidth;
|
|
|
|
// parameters that change per tile
|
|
ushort BatEnt;
|
|
int tileNo, paletteNo, paletteBase;
|
|
byte* src;
|
|
|
|
// calculate tile number and offset for first tile
|
|
int xTile = (xScroll >> 3) & BatRowMask;
|
|
int xOfs = xScroll & 7;
|
|
|
|
// update per-tile parameters for first tile
|
|
BatEnt = BatRow[xTile];
|
|
tileNo = BatEnt & 2047;
|
|
paletteNo = BatEnt >> 12;
|
|
paletteBase = paletteNo * 16;
|
|
src = Patternptr + (tileNo << 6 | yOfs << 3 | xOfs);
|
|
|
|
for (int x = 0; x < FrameWidth; x++)
|
|
{
|
|
if (Priortyptr[x] < priority)
|
|
{
|
|
byte c = *src;
|
|
if (c != 0)
|
|
{
|
|
dst[x] = show ? PALptr[paletteBase + c] : PALptr[0];
|
|
Priortyptr[x] = priority;
|
|
}
|
|
}
|
|
xOfs++;
|
|
src++;
|
|
if (xOfs == 8)
|
|
{
|
|
// update tile number
|
|
xOfs = 0;
|
|
xTile++;
|
|
xTile &= BatRowMask;
|
|
// update per-tile parameters
|
|
BatEnt = BatRow[xTile];
|
|
tileNo = BatEnt & 2047;
|
|
paletteNo = BatEnt >> 12;
|
|
paletteBase = paletteNo * 16;
|
|
src = Patternptr + (tileNo << 6 | yOfs << 3 | xOfs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static byte[] heightTable = { 16, 32, 64, 64 };
|
|
|
|
void RenderSpritesScanline(VDC vdc, byte lowPriority, byte highPriority, bool show)
|
|
{
|
|
if (vdc.SpritesEnabled == false)
|
|
return;
|
|
|
|
// clear inter-sprite priority buffer
|
|
Array.Clear(InterSpritePriorityBuffer, 0, FrameWidth);
|
|
|
|
for (int i = 0; i < 64; i++)
|
|
{
|
|
int y = (vdc.SpriteAttributeTable[(i * 4) + 0] & 1023) - 64;
|
|
int x = (vdc.SpriteAttributeTable[(i * 4) + 1] & 1023) - 32;
|
|
ushort flags = vdc.SpriteAttributeTable[(i * 4) + 3];
|
|
int height = heightTable[(flags >> 12) & 3];
|
|
|
|
if (y + height <= vdc.ActiveLine || y > vdc.ActiveLine)
|
|
continue;
|
|
|
|
int patternNo = (((vdc.SpriteAttributeTable[(i * 4) + 2]) >> 1) & 0x1FF);
|
|
int paletteBase = 256 + ((flags & 15) * 16);
|
|
int width = (flags & 0x100) == 0 ? 16 : 32;
|
|
bool priority = (flags & 0x80) != 0;
|
|
bool hflip = (flags & 0x0800) != 0;
|
|
bool vflip = (flags & 0x8000) != 0;
|
|
|
|
if (width == 32)
|
|
patternNo &= 0x1FE;
|
|
|
|
int yofs;
|
|
if (vflip == false)
|
|
{
|
|
yofs = (vdc.ActiveLine - y) & 15;
|
|
if (height == 32)
|
|
{
|
|
patternNo &= 0x1FD;
|
|
if (vdc.ActiveLine - y >= 16)
|
|
{
|
|
y += 16;
|
|
patternNo += 2;
|
|
}
|
|
}
|
|
else if (height == 64)
|
|
{
|
|
patternNo &= 0x1F9;
|
|
if (vdc.ActiveLine - y >= 48)
|
|
{
|
|
y += 48;
|
|
patternNo += 6;
|
|
}
|
|
else if (vdc.ActiveLine - y >= 32)
|
|
{
|
|
y += 32;
|
|
patternNo += 4;
|
|
}
|
|
else if (vdc.ActiveLine - y >= 16)
|
|
{
|
|
y += 16;
|
|
patternNo += 2;
|
|
}
|
|
}
|
|
}
|
|
else // vflip == true
|
|
{
|
|
yofs = 15 - ((vdc.ActiveLine - y) & 15);
|
|
if (height == 32)
|
|
{
|
|
patternNo &= 0x1FD;
|
|
if (vdc.ActiveLine - y < 16)
|
|
{
|
|
y += 16;
|
|
patternNo += 2;
|
|
}
|
|
}
|
|
else if (height == 64)
|
|
{
|
|
patternNo &= 0x1F9;
|
|
if (vdc.ActiveLine - y < 16)
|
|
{
|
|
y += 48;
|
|
patternNo += 6;
|
|
}
|
|
else if (vdc.ActiveLine - y < 32)
|
|
{
|
|
y += 32;
|
|
patternNo += 4;
|
|
}
|
|
else if (vdc.ActiveLine - y < 48)
|
|
{
|
|
y += 16;
|
|
patternNo += 2;
|
|
}
|
|
}
|
|
}
|
|
if (hflip == false)
|
|
{
|
|
if (x + width > 0 && y + height > 0)
|
|
{
|
|
for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++)
|
|
{
|
|
byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + (xs - x)];
|
|
if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0)
|
|
{
|
|
InterSpritePriorityBuffer[xs] = 1;
|
|
byte myPriority = priority ? highPriority : lowPriority;
|
|
if (PriorityBuffer[xs] < myPriority)
|
|
{
|
|
if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel];
|
|
PriorityBuffer[xs] = myPriority;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (width == 32)
|
|
{
|
|
patternNo++;
|
|
x += 16;
|
|
for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++)
|
|
{
|
|
byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + (xs - x)];
|
|
if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0)
|
|
{
|
|
InterSpritePriorityBuffer[xs] = 1;
|
|
byte myPriority = priority ? highPriority : lowPriority;
|
|
if (PriorityBuffer[xs] < myPriority)
|
|
{
|
|
if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel];
|
|
PriorityBuffer[xs] = myPriority;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // hflip = true
|
|
if (x + width > 0 && y + height > 0)
|
|
{
|
|
if (width == 32)
|
|
patternNo++;
|
|
for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++)
|
|
{
|
|
byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + 15 - (xs - x)];
|
|
if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0)
|
|
{
|
|
InterSpritePriorityBuffer[xs] = 1;
|
|
byte myPriority = priority ? highPriority : lowPriority;
|
|
if (PriorityBuffer[xs] < myPriority)
|
|
{
|
|
if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel];
|
|
PriorityBuffer[xs] = myPriority;
|
|
}
|
|
}
|
|
}
|
|
if (width == 32)
|
|
{
|
|
patternNo--;
|
|
x += 16;
|
|
for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++)
|
|
{
|
|
byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + 15 - (xs - x)];
|
|
if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0)
|
|
{
|
|
InterSpritePriorityBuffer[xs] = 1;
|
|
byte myPriority = priority ? highPriority : lowPriority;
|
|
if (PriorityBuffer[xs] < myPriority)
|
|
{
|
|
if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel];
|
|
PriorityBuffer[xs] = myPriority;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public int[] GetVideoBuffer() { return FrameBuffer; }
|
|
|
|
public int VirtualWidth { get { return FrameWidth; } }
|
|
public int VirtualHeight { get { return FrameHeight; } }
|
|
public int BufferWidth { get { return FrameWidth; } }
|
|
public int BufferHeight { get { return FrameHeight; } }
|
|
public int BackgroundColor { get { return VCE.Palette[0]; } }
|
|
}
|
|
}
|