diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs index fb3c6b9bd6..8084f7e818 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IEmulator.cs @@ -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; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs index 852b746476..4384ac22ca 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.ISoundProvider.cs @@ -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]; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs index 90138540f7..a41226f139 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IStatable.cs @@ -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); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs index 935c7c28d3..adf29e2ce6 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/ChannelF.IVideoProvider.cs @@ -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 { /// /// 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) /// - 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]; } + /// + /// Called after every CPU clock + /// + 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; + */ } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs index 76548cb2ff..06dd4c0f9b 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Fairchild/ChannelF/Ports.cs @@ -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) {