289 lines
7.0 KiB
C#
289 lines
7.0 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
|
|
{
|
|
public sealed partial class Vic
|
|
{
|
|
private const int BorderLeft38 = 0x023;
|
|
private const int BorderLeft40 = 0x01C;
|
|
private const int BorderRight38 = 0x153;
|
|
private const int BorderRight40 = 0x15C;
|
|
private const int BorderTop25 = 0x033;
|
|
private const int BorderTop24 = 0x037;
|
|
private const int BorderBottom25 = 0x0FB;
|
|
private const int BorderBottom24 = 0x0F7;
|
|
private const int FirstDmaLine = 0x030;
|
|
private const int LastDmaLine = 0x0F7;
|
|
|
|
// The special actions taken by the Vic are in the same order and interval on all chips, just different offsets.
|
|
private static readonly int[] TimingBuilderCycle14Act =
|
|
{
|
|
PipelineUpdateVc, 0,
|
|
PipelineSpriteCrunch, 0,
|
|
PipelineUpdateMcBase, 0,
|
|
};
|
|
|
|
// This builds a table of special actions to take on each half-cycle. Cycle14 is the X-raster position where
|
|
// pre-display operations happen, and Cycle55 is the X-raster position where post-display operations happen.
|
|
public static int[] TimingBuilder_Act(int[] timing, int cycle14, int sprite0Ba, int sprDisp)
|
|
{
|
|
var result = new List<int>();
|
|
|
|
var length = timing.Length;
|
|
for (var i = 0; i < length; i++)
|
|
{
|
|
while (i < result.Count)
|
|
i++;
|
|
if (timing[i] == cycle14)
|
|
result.AddRange(TimingBuilderCycle14Act);
|
|
else
|
|
result.Add(0);
|
|
}
|
|
for (var i = 0; i < length; i++)
|
|
{
|
|
// pipeline raster X delay
|
|
if (timing[(i + 1) % length] == timing[i])
|
|
result[i] |= PipelineHoldX;
|
|
|
|
// pipeline border checks
|
|
if (timing[i] == (BorderLeft40 & 0xFFC))
|
|
result[i] |= PipelineBorderLeft1;
|
|
if (timing[i] == (BorderLeft38 & 0xFFC))
|
|
result[i] |= PipelineBorderLeft0;
|
|
if (timing[i] == (BorderRight38 & 0xFFC))
|
|
result[i] |= PipelineBorderRight0;
|
|
if (timing[i] == (BorderRight40 & 0xFFC))
|
|
result[i] |= PipelineBorderRight1;
|
|
|
|
// right side timing
|
|
if (timing[i] == 0x0158)
|
|
result[i] |= PipelineSpriteExpansion;
|
|
if (timing[i] == 0x0168)
|
|
result[i] |= PipelineUpdateRc;
|
|
if (timing[i] == sprite0Ba || timing[i] == sprite0Ba + 8)
|
|
result[i] |= PipelineSpriteDma;
|
|
if (timing[i] == sprDisp)
|
|
result[i] |= PipelineSpriteDisplay;
|
|
|
|
}
|
|
|
|
return result.ToArray();
|
|
}
|
|
|
|
// This builds a table of how the BA pin is supposed to act on each half-cycle.
|
|
public static int[] TimingBuilder_BA(int[] fetch)
|
|
{
|
|
const int baRestart = 7;
|
|
var start = 0;
|
|
var length = fetch.Length;
|
|
var result = new int[length];
|
|
var spriteBa = new int[8];
|
|
var charBa = 0;
|
|
|
|
while (true)
|
|
{
|
|
if (fetch[start] == FetchTypeSprite)
|
|
break;
|
|
start++;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
if (fetch[start] == FetchTypeColor)
|
|
break;
|
|
start--;
|
|
}
|
|
|
|
if (start < 0)
|
|
start += length;
|
|
var offset = start;
|
|
|
|
while (true)
|
|
{
|
|
var ba = BaTypeNone;
|
|
|
|
if (fetch[offset] == FetchTypeColor)
|
|
charBa = baRestart;
|
|
else if ((fetch[offset] & 0xFF00) == FetchTypeSprite)
|
|
spriteBa[fetch[offset] & 0x007] = baRestart;
|
|
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
if (spriteBa[i] > 0)
|
|
{
|
|
ba <<= 4;
|
|
ba |= i;
|
|
spriteBa[i]--;
|
|
}
|
|
}
|
|
ba &= 0x0FFF;
|
|
|
|
if (charBa > 0)
|
|
{
|
|
ba = BaTypeCharacter;
|
|
charBa--;
|
|
}
|
|
|
|
result[offset] = ba;
|
|
|
|
offset--;
|
|
if (offset < 0)
|
|
offset += length;
|
|
|
|
if (offset == start)
|
|
break;
|
|
}
|
|
|
|
for (var i = 0; i < length; i += 2)
|
|
{
|
|
result[i] = result[i + 1];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// This builds a table of the fetch operations to take on each half-cycle.
|
|
public static int[] TimingBuilder_Fetch(int[] timing, int sprite)
|
|
{
|
|
var length = timing.Length;
|
|
var result = new int[length];
|
|
var index = -1;
|
|
var refreshCounter = 0;
|
|
var spriteActive = false;
|
|
var spriteIndex = 0;
|
|
var spritePhase = 0;
|
|
var charCounter = 0;
|
|
|
|
for (var i = 0; i < length; i++)
|
|
{
|
|
result[i++] = FetchTypeIdle;
|
|
result[i] = FetchTypeNone;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
index++;
|
|
if (index >= length)
|
|
index -= length;
|
|
var offset = timing[index];
|
|
|
|
if (charCounter > 0)
|
|
{
|
|
result[index] = (charCounter & 1) == 0 ? FetchTypeColor : FetchTypeGraphics;
|
|
charCounter--;
|
|
if (charCounter == 0)
|
|
break;
|
|
}
|
|
|
|
if (refreshCounter > 0)
|
|
{
|
|
result[index] = (refreshCounter & 1) == 0 ? FetchTypeNone : FetchTypeRefresh;
|
|
refreshCounter--;
|
|
if (refreshCounter == 0)
|
|
charCounter = 80;
|
|
}
|
|
|
|
if (offset == sprite)
|
|
{
|
|
spriteActive = true;
|
|
}
|
|
|
|
if (spriteActive)
|
|
{
|
|
result[index] = (spriteIndex | (spritePhase << 4));
|
|
spritePhase++;
|
|
if (spritePhase == 4)
|
|
{
|
|
spritePhase = 0;
|
|
spriteIndex++;
|
|
if (spriteIndex == 8)
|
|
{
|
|
spriteActive = false;
|
|
refreshCounter = 9;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result.ToArray();
|
|
}
|
|
|
|
// This uses the vBlank values to determine the height of the visible screen.
|
|
private static int TimingBuilder_ScreenHeight(int vblankStart, int vblankEnd, int lines)
|
|
{
|
|
if (vblankStart < 0 || vblankEnd < 0)
|
|
{
|
|
return lines;
|
|
}
|
|
|
|
var offset = vblankEnd;
|
|
var result = 0;
|
|
while (true)
|
|
{
|
|
if (offset >= lines)
|
|
offset -= lines;
|
|
if (offset == vblankStart)
|
|
return result;
|
|
offset++;
|
|
result++;
|
|
}
|
|
}
|
|
|
|
// This uses the hBlank values to determine the width of the visible screen.
|
|
private static int TimingBuilder_ScreenWidth(IList<int> timing, int hblankStart, int hblankEnd)
|
|
{
|
|
if (hblankStart < 0 || hblankEnd < 0)
|
|
{
|
|
return timing.Count * 4;
|
|
}
|
|
|
|
var length = timing.Count;
|
|
var result = 0;
|
|
var offset = 0;
|
|
|
|
while (timing[offset] != hblankEnd) { offset = (offset + 1) % length; }
|
|
while (timing[offset] != hblankStart) { offset = (offset + 1) % length; result++; }
|
|
|
|
return (result * 4);
|
|
}
|
|
|
|
// This builds the table of X-raster positions. Start marks the position where the
|
|
// Y-raster is incremented. Width is the position where the X-raster is reset to zero. Count
|
|
// is the width of a rasterline in pixels. DelayOffset is the X-raster position where lag begins
|
|
// (specifically on an NTSC 6567R8) and DelayAmount is the number of positions to lag.
|
|
public static int[] TimingBuilder_XRaster(int start, int width, int count, int delayOffset, int delayAmount)
|
|
{
|
|
var result = new List<int>();
|
|
var rasterX = start;
|
|
var delayed = false;
|
|
count >>= 2;
|
|
delayAmount >>= 2;
|
|
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
result.Add(rasterX);
|
|
|
|
if (!delayed)
|
|
{
|
|
rasterX += 4;
|
|
if (rasterX >= width)
|
|
rasterX -= width;
|
|
}
|
|
else
|
|
{
|
|
delayAmount--;
|
|
if (delayAmount <= 0)
|
|
delayed = false;
|
|
continue;
|
|
}
|
|
|
|
if (rasterX == delayOffset && delayAmount > 0)
|
|
delayed = true;
|
|
}
|
|
|
|
return result.ToArray();
|
|
}
|
|
}
|
|
}
|