[ChannelFHawk] Approximate the clocking of video circuitry rather than generating the framebuffer from RAM at the end of a frame
This commit is contained in:
parent
404f28fa64
commit
789a6266fc
|
@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
|
||||
public bool DeterministicEmulation { get; set; }
|
||||
|
||||
public int ClockPerFrame;
|
||||
public int CpuClocksPerFrame;
|
||||
public int FrameClock;
|
||||
|
||||
private void CalcClock()
|
||||
|
@ -23,6 +23,15 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
int pixelClocksPerFrame;
|
||||
if (Region == DisplayType.NTSC)
|
||||
{
|
||||
HTotal = 256;
|
||||
HBlankOff = 8;
|
||||
HBlankOn = 212;
|
||||
VTotal = 264;
|
||||
VBlankOff = 16;
|
||||
VBlankOn = 248;
|
||||
ScanlineRepeats = 4;
|
||||
PixelWidth = 2;
|
||||
|
||||
// NTSC CPU speed is NTSC Colorburst / 2
|
||||
const double NTSC_COLORBURST = 4500000 * 227.5 / 286;
|
||||
cpuFreq = NTSC_COLORBURST / 2;
|
||||
|
@ -37,6 +46,15 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
}
|
||||
else
|
||||
{
|
||||
HTotal = 256;
|
||||
HBlankOff = 8;
|
||||
HBlankOn = 212;
|
||||
VTotal = 312;
|
||||
VBlankOff = 20;
|
||||
VBlankOn = 310;
|
||||
ScanlineRepeats = 5;
|
||||
PixelWidth = 2;
|
||||
|
||||
if (version == ConsoleVersion.ChannelF)
|
||||
{
|
||||
// PAL CPU speed is 2MHz
|
||||
|
@ -77,7 +95,9 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
}
|
||||
|
||||
var c = cpuFreq * pixelClocksPerFrame / pixelClock;
|
||||
ClockPerFrame = (int) c;
|
||||
CpuClocksPerFrame = (int) c;
|
||||
PixelClocksPerCpuClock = pixelClock / cpuFreq;
|
||||
PixelClocksPerFrame = pixelClocksPerFrame;
|
||||
|
||||
SetupAudio();
|
||||
}
|
||||
|
@ -98,9 +118,10 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
|
||||
PollInput();
|
||||
|
||||
while (FrameClock++ < ClockPerFrame)
|
||||
while (FrameClock++ < CpuClocksPerFrame)
|
||||
{
|
||||
CPU.ExecuteOne();
|
||||
ClockVideo();
|
||||
}
|
||||
|
||||
FrameClock = 0;
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
{
|
||||
_samplesPerFrame = (int)(SAMPLE_RATE * VsyncDenominator / VsyncNumerator);
|
||||
// TODO: more precise audio clocking
|
||||
_cyclesPerSample = ClockPerFrame / (double)_samplesPerFrame;
|
||||
_cyclesPerSample = CpuClocksPerFrame / (double)_samplesPerFrame;
|
||||
_sampleBuffer = new short[_samplesPerFrame];
|
||||
_filteredSampleBuffer = new double[_samplesPerFrame];
|
||||
_toneBuffer = new int[_samplesPerFrame];
|
||||
|
|
|
@ -8,13 +8,13 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
{
|
||||
ser.BeginSection("ChannelF");
|
||||
ser.Sync(nameof(VRAM), ref VRAM, false);
|
||||
ser.Sync(nameof(latch_colour), ref latch_colour);
|
||||
ser.Sync(nameof(latch_x), ref latch_x);
|
||||
ser.Sync(nameof(latch_y), ref latch_y);
|
||||
ser.Sync(nameof(_latch_colour), ref _latch_colour);
|
||||
ser.Sync(nameof(_latch_x), ref _latch_x);
|
||||
ser.Sync(nameof(_latch_y), ref _latch_y);
|
||||
ser.Sync(nameof(_pixelClocksRemaining), ref _pixelClocksRemaining);
|
||||
|
||||
ser.Sync(nameof(FrameClock), ref FrameClock);
|
||||
ser.Sync(nameof(_frame), ref _frame);
|
||||
ser.Sync(nameof(ClockPerFrame), ref ClockPerFrame);
|
||||
ser.Sync(nameof(_isLag), ref _isLag);
|
||||
ser.Sync(nameof(_lagCount), ref _lagCount);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using BizHawk.Emulation.Cores.Nintendo.NES;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
||||
{
|
||||
|
@ -7,9 +8,10 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
{
|
||||
/// <summary>
|
||||
/// 128x64 pixels - 8192x2bits (2 KB)
|
||||
/// For the purposes of this core we will use 8192 bytes and just & 0x03
|
||||
/// For the purposes of this core we will use 8192 bytes and just mask 0x03
|
||||
/// (Also adding an additional 10 rows to the RAM buffer so that it's more aligned with the actual display)
|
||||
/// </summary>
|
||||
public byte[] VRAM = new byte[(128 * 64)];
|
||||
public byte[] VRAM = new byte[128 * (64 + 10)];
|
||||
|
||||
|
||||
public static readonly int[] FPalette =
|
||||
|
@ -23,8 +25,7 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
Colors.ARGB(0x4B, 0x3F, 0xF3), // Blue
|
||||
Colors.ARGB(0xE0, 0xE0, 0xE0), // Gray
|
||||
Colors.ARGB(0x91, 0xFF, 0xA6), // BGreen
|
||||
Colors.ARGB(0xCE, 0xD0, 0xFF), // BBlue
|
||||
|
||||
Colors.ARGB(0xCE, 0xD0, 0xFF), // BBlue
|
||||
};
|
||||
|
||||
public static readonly int[] CMap =
|
||||
|
@ -35,21 +36,147 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
6, 4, 2, 3,
|
||||
};
|
||||
|
||||
private int latch_colour = 2; //2;
|
||||
private int latch_x;
|
||||
private int latch_y;
|
||||
|
||||
private int scanlineRepeats;
|
||||
private int _latch_colour = 2;
|
||||
private int _latch_x;
|
||||
private int _latch_y;
|
||||
|
||||
private int[] frameBuffer;
|
||||
private int[] outputBuffer;
|
||||
private int[] videoBuffer;
|
||||
private double _pixelClockCounter;
|
||||
private double _pixelClocksRemaining;
|
||||
|
||||
|
||||
private int ScanlineRepeats;
|
||||
private int PixelWidth;
|
||||
private int HTotal;
|
||||
private int HBlankOff;
|
||||
private int HBlankOn;
|
||||
private int VTotal;
|
||||
private int VBlankOff;
|
||||
private int VBlankOn;
|
||||
private double PixelClocksPerCpuClock;
|
||||
private double PixelClocksPerFrame;
|
||||
|
||||
public void SetupVideo()
|
||||
{
|
||||
scanlineRepeats = Region == DisplayType.NTSC ? 4 : 5;
|
||||
frameBuffer = new int[128 * 64];
|
||||
outputBuffer = new int[128 * 2 * 64 * scanlineRepeats];
|
||||
|
||||
outputBuffer = new int[(HBlankOn - HBlankOff) * (VBlankOn - VBlankOff)];
|
||||
|
||||
videoBuffer = new int[HTotal * VTotal];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after every CPU clock
|
||||
/// </summary>
|
||||
private void ClockVideo()
|
||||
{
|
||||
while (_pixelClocksRemaining > 1)
|
||||
{
|
||||
var currScanline = (int)(_pixelClockCounter / HTotal);
|
||||
var currPixelInLine = (int)(_pixelClockCounter % HTotal);
|
||||
var currRowInVram = currScanline / ScanlineRepeats;
|
||||
var currColInVram = currPixelInLine / PixelWidth;
|
||||
|
||||
if (currScanline < VBlankOff || currScanline >= VBlankOn)
|
||||
{
|
||||
// vertical flyback
|
||||
}
|
||||
else if (currPixelInLine < HBlankOff || currPixelInLine >= HBlankOn)
|
||||
{
|
||||
// horizontal flyback
|
||||
}
|
||||
else
|
||||
{
|
||||
// active display
|
||||
var p1 = (VRAM[(currRowInVram * 0x80) + 125]) & 0x03;
|
||||
var p2 = (VRAM[(currRowInVram * 0x80) + 126]) & 0x03;
|
||||
var pOffset = ((p2 & 0x02) | (p1 >> 1)) << 2;
|
||||
|
||||
var colourIndex = pOffset + (VRAM[currColInVram | (currRowInVram << 7)] & 0x03);
|
||||
videoBuffer[(currScanline * HTotal) + currPixelInLine] = FPalette[CMap[colourIndex]];
|
||||
}
|
||||
|
||||
_pixelClockCounter++;
|
||||
_pixelClocksRemaining -= 1;
|
||||
}
|
||||
|
||||
_pixelClocksRemaining += PixelClocksPerCpuClock;
|
||||
_pixelClockCounter %= PixelClocksPerFrame;
|
||||
}
|
||||
|
||||
private int HDisplayable => HBlankOn - HBlankOff;
|
||||
private int VDisplayable => VBlankOn - VBlankOff;
|
||||
|
||||
private double HPixelAspectModifier => Region == DisplayType.NTSC ? 1.75 : 1.95; // This is only here because the aspect ratio is off between regions. It could maybe be negated by trimming the number of scanlines on PAL??
|
||||
|
||||
private int[] ClampBuffer(int[] buffer, int originalWidth, int originalHeight, int trimLeft, int trimTop, int trimRight, int trimBottom)
|
||||
{
|
||||
int newWidth = originalWidth - trimLeft - trimRight;
|
||||
int newHeight = originalHeight - trimTop - trimBottom;
|
||||
int[] newBuffer = new int[newWidth * newHeight];
|
||||
|
||||
for (int y = 0; y < newHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < newWidth; x++)
|
||||
{
|
||||
int originalIndex = (y + trimTop) * originalWidth + (x + trimLeft);
|
||||
int newIndex = y * newWidth + x;
|
||||
newBuffer[newIndex] = buffer[originalIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
private static double GetHorizontalModifier(int bufferWidth, int bufferHeight, double targetAspectRatio)
|
||||
{
|
||||
// Calculate the current aspect ratio
|
||||
double currentAspectRatio = (double)bufferWidth / bufferHeight;
|
||||
|
||||
// Calculate the horizontal modifier needed to achieve the target aspect ratio
|
||||
double horizontalModifier = targetAspectRatio / currentAspectRatio;
|
||||
|
||||
return horizontalModifier;
|
||||
}
|
||||
|
||||
private static double GetVerticalModifier(int bufferWidth, int bufferHeight, double targetAspectRatio)
|
||||
{
|
||||
// Calculate the current aspect ratio
|
||||
double currentAspectRatio = (double)bufferWidth / bufferHeight;
|
||||
|
||||
// Calculate the vertical modifier needed to achieve the target aspect ratio
|
||||
double verticalModifier = currentAspectRatio / targetAspectRatio;
|
||||
|
||||
return verticalModifier;
|
||||
}
|
||||
|
||||
public int VirtualWidth => HDisplayable * 2;
|
||||
public int VirtualHeight => (int)(VDisplayable * GetVerticalModifier(HDisplayable, VDisplayable, 4.0/3.0)) * 2;
|
||||
public int BufferWidth => HDisplayable;
|
||||
public int BufferHeight => VDisplayable;
|
||||
public int BackgroundColor => Colors.ARGB(0xFF, 0xFF, 0xFF);
|
||||
public int VsyncNumerator { get; private set; }
|
||||
public int VsyncDenominator { get; private set; }
|
||||
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
// https://channelf.se/veswiki/index.php?title=VRAM
|
||||
// 'The emulator MESS uses a fixed 102x58 resolution starting at (4,4) but the safe area for a real system is about 95x58 pixels'
|
||||
// 'Note that the pixel aspect is a lot closer to widescreen (16:9) than standard definition (4:3). On a TV from the 70's or 80's pixels are rectangular, standing up. In widescreen mode they are close to perfect squares'
|
||||
// https://channelf.se/veswiki/index.php?title=Resolution
|
||||
// 'Even though PAL televisions system has more lines vertically, the Channel F displays about the same as on the original NTSC video system'
|
||||
//
|
||||
// Right now we are just trimming based on the HBLANK and VBLANK values (we might need to go further like the other emulators)
|
||||
// VirtualWidth is being used to force the aspect ratio into 4:3
|
||||
// On real hardware it looks like this (so we are close): https://www.youtube.com/watch?v=ZvQA9tiEIuQ
|
||||
return ClampBuffer(videoBuffer, HTotal, VTotal, HBlankOff, VBlankOff, HTotal - HBlankOn, VTotal - VBlankOn);
|
||||
}
|
||||
|
||||
public DisplayType Region => region == RegionType.NTSC ? DisplayType.NTSC : DisplayType.PAL;
|
||||
|
||||
/*
|
||||
private void BuildFrameFromRAM()
|
||||
{
|
||||
for (int r = 0; r < 64; r++)
|
||||
|
@ -67,69 +194,6 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExpandFrame()
|
||||
{
|
||||
int initialWidth = 128;
|
||||
int initialHeight = 64;
|
||||
|
||||
for (int lines = 0; lines < initialHeight; lines++)
|
||||
{
|
||||
for (int i = 0; i < scanlineRepeats; i++)
|
||||
{
|
||||
for (int x = 0; x < initialWidth; x++)
|
||||
{
|
||||
for (int j = 0; j < 2; j++)
|
||||
{
|
||||
outputBuffer[(lines * scanlineRepeats + i) * initialWidth * 2 + x * 2 + j] = FPalette[frameBuffer[lines * initialWidth + x]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public int lBorder => 8;
|
||||
public int rBorder => 52;
|
||||
public int tBorder => 8;
|
||||
public int bBorder => 4;
|
||||
|
||||
public int VirtualWidth => (int)((BufferWidth - lBorder - rBorder) * 2.2);
|
||||
public int VirtualHeight => Region == DisplayType.NTSC ? BufferHeight - tBorder - bBorder : (int)((BufferHeight - tBorder - bBorder) * 0.8);
|
||||
public int BufferWidth => 256 - lBorder - rBorder;
|
||||
public int BufferHeight => (64 * scanlineRepeats) - tBorder - bBorder;
|
||||
public int BackgroundColor => Colors.ARGB(0xFF, 0xFF, 0xFF);
|
||||
public int VsyncNumerator { get; private set; }
|
||||
public int VsyncDenominator { get; private set; }
|
||||
|
||||
public int[] TrimOutputBuffer(int[] buff, int leftTrim, int topTrim, int rightTrim, int bottomTrim)
|
||||
{
|
||||
int initialWidth = 128 * 2;
|
||||
int initialHeight = 64 * scanlineRepeats;
|
||||
int newWidth = initialWidth - leftTrim - rightTrim;
|
||||
int newHeight = initialHeight - topTrim - bottomTrim;
|
||||
|
||||
int[] trimmedBuffer = new int[newWidth * newHeight];
|
||||
|
||||
for (int y = 0; y < newHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < newWidth; x++)
|
||||
{
|
||||
trimmedBuffer[y * newWidth + x] = buff[(y + topTrim) * initialWidth + (x + leftTrim)];
|
||||
}
|
||||
}
|
||||
|
||||
return trimmedBuffer;
|
||||
}
|
||||
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
BuildFrameFromRAM();
|
||||
ExpandFrame();
|
||||
return TrimOutputBuffer(outputBuffer, lBorder, tBorder, rBorder, bBorder);
|
||||
}
|
||||
|
||||
public DisplayType Region => region == RegionType.NTSC ? DisplayType.NTSC : DisplayType.PAL;
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,24 +107,24 @@ namespace BizHawk.Emulation.Cores.Consoles.ChannelF
|
|||
// WRT pulse
|
||||
// pulse clocks the 74195 parallel access shift register which feeds inputs of 2 NAND gates
|
||||
// writing data to both sets of even and odd VRAM chips (based on the row and column addresses latched into the 7493 ICs)
|
||||
VRAM[((latch_y) * 0x80) + latch_x] = (byte)latch_colour;
|
||||
VRAM[((_latch_y) * 0x80) + _latch_x] = (byte)_latch_colour;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 1:
|
||||
OutputLatch[addr] = value;
|
||||
latch_colour = ((value ^ 0xFF) >> 6) & 0x03;
|
||||
_latch_colour = ((value ^ 0xFF) >> 6) & 0x03;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
OutputLatch[addr] = value;
|
||||
latch_x = (value | 0x80) ^ 0xFF;
|
||||
_latch_x = (value | 0x80) ^ 0xFF;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
OutputLatch[addr] = value;
|
||||
latch_y = (value | 0xC0) ^ 0xFF;
|
||||
_latch_y = (value | 0xC0) ^ 0xFF;
|
||||
var audio = ((value) >> 6) & 0x03;
|
||||
if (audio != _tone)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue