ULA Implemented. Scanline renderer so cycle accurate
This commit is contained in:
128
Core/Io/ULA.cs
Normal file
128
Core/Io/ULA.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
using Core.Memory;
|
||||
using System;
|
||||
|
||||
namespace Core.Io
|
||||
{
|
||||
public class ULA
|
||||
{
|
||||
private readonly MemoryBus _memoryBus;
|
||||
private readonly IO_Bus _simpleIoBus;
|
||||
|
||||
// The ULA owns the frame buffer now!
|
||||
public const int ScreenWidth = 320;
|
||||
public const int ScreenHeight = 256; // Perfectly cropped size
|
||||
public int[] FrameBuffer { get; private set; }
|
||||
|
||||
private int _ulaFrameCount = 0;
|
||||
|
||||
// The hardware color palette belongs to the ULA, not the Windows Form!
|
||||
private readonly int[] SpectrumColors = new int[]
|
||||
{
|
||||
unchecked((int)0xFF000000), unchecked((int)0xFF0000D7),
|
||||
unchecked((int)0xFFD70000), unchecked((int)0xFFD700D7),
|
||||
unchecked((int)0xFF00D700), unchecked((int)0xFF00D7D7),
|
||||
unchecked((int)0xFFD7D700), unchecked((int)0xFFD7D7D7),
|
||||
|
||||
unchecked((int)0xFF000000), unchecked((int)0xFF0000FF),
|
||||
unchecked((int)0xFFFF0000), unchecked((int)0xFFFF00FF),
|
||||
unchecked((int)0xFF00FF00), unchecked((int)0xFF00FFFF),
|
||||
unchecked((int)0xFFFFFF00), unchecked((int)0xFFFFFFFF)
|
||||
};
|
||||
|
||||
public ULA(MemoryBus memoryBus, IO_Bus ioBus)
|
||||
{
|
||||
_memoryBus = memoryBus;
|
||||
_simpleIoBus = ioBus;
|
||||
FrameBuffer = new int[ScreenWidth * ScreenHeight];
|
||||
}
|
||||
|
||||
// We will wire this up to the Z80 in Phase 3 so the CPU stays system-agnostic!
|
||||
public int GetContentionDelay(ushort address, long currentTStates)
|
||||
{
|
||||
if (address < 0x4000 || address > 0x7FFF) return 0;
|
||||
long frameT = currentTStates % 69888;
|
||||
if (frameT < 14336 || frameT >= 57344) return 0;
|
||||
int lineT = (int)(frameT % 224);
|
||||
if (lineT < 14 || lineT > 141) return 0;
|
||||
|
||||
int delayOffset = (lineT - 14) % 8;
|
||||
int[] delayPattern = { 6, 5, 4, 3, 2, 1, 0, 0 };
|
||||
return delayPattern[delayOffset];
|
||||
}
|
||||
|
||||
// The perfectly cropped, 320x256 Scanline Renderer
|
||||
public void RenderScanline(int scanline)
|
||||
{
|
||||
// 1. Drop the invisible lines instantly (VBlank/Overscan)
|
||||
if (scanline < 32 || scanline > 287) return;
|
||||
|
||||
// 2. Calculate our visual Y coordinate (0 to 255) for the bitmap array
|
||||
int renderY = scanline - 32;
|
||||
int currentBorderColor = SpectrumColors[_simpleIoBus.BorderColorIndex];
|
||||
|
||||
// --- Are we in the Top or Bottom Border? ---
|
||||
if (scanline < 64 || scanline > 255)
|
||||
{
|
||||
int yOffset = renderY * ScreenWidth;
|
||||
for (int x = 0; x < ScreenWidth; x++)
|
||||
{
|
||||
FrameBuffer[yOffset + x] = currentBorderColor;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// --- We are in the Visible Display Area (Lines 64 to 255) ---
|
||||
int y = scanline - 64;
|
||||
|
||||
// Handle Flash Phase (only increment once per frame when y == 0)
|
||||
if (y == 0) _ulaFrameCount++;
|
||||
bool invertFlashPhase = (_ulaFrameCount % 32) >= 16;
|
||||
|
||||
int rowStartIndex = renderY * ScreenWidth;
|
||||
|
||||
// Draw the 32 pixels of left border
|
||||
for (int b = 0; b < 32; b++) FrameBuffer[rowStartIndex + b] = currentBorderColor;
|
||||
|
||||
int third = y / 64;
|
||||
int characterRow = (y % 64) / 8;
|
||||
int pixelRow = y % 8;
|
||||
|
||||
// Draw the 32 horizontal character blocks of the visible screen
|
||||
for (int col = 0; col < 32; col++)
|
||||
{
|
||||
ushort pixelAddress = (ushort)(0x4000 | (third << 11) | (pixelRow << 8) | (characterRow << 5) | col);
|
||||
ushort attrAddress = (ushort)(0x5800 + (y / 8) * 32 + col);
|
||||
|
||||
byte pixels = _memoryBus.Read(pixelAddress);
|
||||
byte attr = _memoryBus.Read(attrAddress);
|
||||
|
||||
int ink = attr & 0x07;
|
||||
int paper = (attr >> 3) & 0x07;
|
||||
int brightOffset = (attr & 0x40) != 0 ? 8 : 0;
|
||||
bool isFlashSet = (attr & 0x80) != 0;
|
||||
|
||||
int inkColor = SpectrumColors[ink + brightOffset];
|
||||
int paperColor = SpectrumColors[paper + brightOffset];
|
||||
|
||||
if (isFlashSet && invertFlashPhase)
|
||||
{
|
||||
int temp = inkColor; inkColor = paperColor; paperColor = temp;
|
||||
}
|
||||
|
||||
// Draw the 8 pixels in this block
|
||||
for (int bit = 0; bit < 8; bit++)
|
||||
{
|
||||
bool isPixelSet = (pixels & (1 << (7 - bit))) != 0;
|
||||
int renderX = 32 + (col * 8) + bit;
|
||||
FrameBuffer[rowStartIndex + renderX] = isPixelSet ? inkColor : paperColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the 32 pixels of right border
|
||||
for (int b = ScreenWidth - 32; b < ScreenWidth; b++)
|
||||
{
|
||||
FrameBuffer[rowStartIndex + b] = currentBorderColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user