private void RunVBlank() { if (lcdClock >= 144) { lcdClock -= 144; LY++; // Move to next line // If LY == LYC, set the flag and raise interrupt if it's enabled matchFlag = LY == LYC; if (matchFlag && (enabledInterrupts & GPUInterrupts.OnMatch) != 0) { LCDCInterrupt?.Invoke(); } if (LY == 154) { renderTask.Wait(); // Wait for the frame to finish rendering // Start rendering the next frame LY = 0; if (LY == LYC && (enabledInterrupts & GPUInterrupts.OnMatch) != 0) { LCDCInterrupt?.Invoke(); } lcdMode = GPUMode.OAMRead; if ((enabledInterrupts & GPUInterrupts.OnOAMRead) != 0) { LCDCInterrupt?.Invoke(); } renderTask = Task.Run(() => LoadSpriteData()); } } }
/// <summary> /// LCD reading from OAM only /// </summary> private void Mode2() { if (_modeClock >= 80) { Mode = GPUMode.Mode3; } }
internal void Step(uint instructionTime) { if (!LCDC.HasFlag(LCDC.LCDEnable)) { return; } _modeClock += instructionTime; GPUMode mode = Mode; switch (mode) { case GPUMode.Mode0: // HBlank Mode0(); break; case GPUMode.Mode1: // VBlank Mode1(); break; case GPUMode.Mode2: // OAM Mode2(); break; case GPUMode.Mode3: // LCD Transfer Mode3(); break; default: break; } }
/// <summary> /// LCD Transfer of the data from the OAM/Vram to the frame /// This is where the frame data is built /// </summary> private void Mode3() { if (_modeClock >= 172) { Scanline(); Mode = GPUMode.Mode0; } }
private void RunOAMRead() { if (lcdClock >= 20) { renderTask?.Wait(); // Wait for sprite load to finish lcdClock -= 20; // Pre-render the line lcdMode = GPUMode.VRAMRead; renderTask = Task.Run(() => PreRender()); } }
/// <summary> /// VBlank /// Reports a new frame to display /// </summary> private void Mode1() { var line = _modeClock % 456; LY = (byte)(144 + line); if (_modeClock >= 4560) { Mode = GPUMode.Mode2; LY = 0; VBlank?.Invoke(this, EventArgs.Empty); } }
public void TestGPUMode3ToMode0Timing() { CPU cpu = new CPU(new GPU()); MMU mmu = new MMU(); var rom = new ROM(); cpu.GPU.LCDC = LCDC.LCDEnable; decimal timeSpentMicroSec = 0; while (cpu.GPU.Mode != GPUMode.Mode3) { cpu.Exec(0x00); if (cpu.InstructionsCount > 10000) { Assert.Fail("GPU did not changed to correct state"); } } GPUMode gpuMode = cpu.GPU.Mode; while (true) { cpu.Exec(0x00); timeSpentMicroSec += (decimal)1.0 / cpu.ClockFrequencyMhz * cpu.LastInstructionClockTime; var newMode = cpu.GPU.Mode; if (gpuMode != newMode) { if (newMode == GPUMode.Mode0) { Assert.IsTrue(timeSpentMicroSec > 41); Assert.IsTrue(timeSpentMicroSec < 42M); return; } else { Assert.Fail("GPU Mode should transition from Mode0 to Mode2"); } } if (cpu.InstructionsCount > 10000) { Assert.Fail("GPU did not changed to correct state"); } } }
private void RunVRAMRead() { if (lcdClock >= 43) { renderTask.Wait(); // Wait for pre-rendering to finish lcdClock -= 43; // Switch to HBlank mode lcdMode = GPUMode.HBlank; if ((enabledInterrupts & GPUInterrupts.OnHBlank) != 0) { LCDCInterrupt?.Invoke(); } // No task is run during HBlank } }
private void EnableLCD(bool enable) { if (lcdEnabled && !enable) { // Disable the LCD lcdEnabled = false; lcdClock = 0; LY = 0; } else if (!lcdEnabled && enable) { // Enable the LCD lcdEnabled = true; lcdMode = GPUMode.OAMRead; } }
/// <summary> /// HBlank /// </summary> private void Mode0() { if (_modeClock >= 204) { var y = LY++; if (LY > 143) { Mode = GPUMode.Mode1; #warning Manage VBlank interrupt ? } else { Mode = GPUMode.Mode2; } } }
public GPU(Clock cpuClock, GPURegisters gpuRegisters) { this.cpuClock = cpuClock; this.gpuRegisters = gpuRegisters; clock = new Clock(); gpuMode = GPUMode.HBlankPeriod; memoryData = new byte[0x2000]; tileSet = new byte[0x17FF]; tileBackgroundMap = new byte[0x7FF]; oamData = new byte[0xA0]; zRamData = new byte[0x7F]; bmp.Save("D:\\test.bmp"); }
public void TestGPUMode0ToMode2Timing() { CPU cpu = new CPU(new GPU()); MMU mmu = new MMU(); var rom = new ROM(); cpu.GPU.LCDC = LCDC.LCDEnable; cpu.GPU.Mode = GPUMode.Mode0; decimal timeSpentMicroSec = 0; GPUMode gpuMode = cpu.GPU.Mode; while (true) { cpu.Exec(0x00); timeSpentMicroSec += (decimal)1.0 / cpu.ClockFrequencyMhz * cpu.LastInstructionClockTime; var newMode = cpu.GPU.Mode; if (gpuMode != newMode) { if (newMode == GPUMode.Mode2) { // We got the new mode, check that timings are ok // Mode0 should stay up for 204 clock, meaning 48.6µsec Assert.IsTrue(timeSpentMicroSec > 48.5M); Assert.IsTrue(timeSpentMicroSec < 48.7M); return; } else { Assert.Fail("GPU Mode should transition from Mode0 to Mode2"); } } if (cpu.InstructionsCount > 100000) { Assert.Fail("GPU did not changed to correct state"); } } }
private void RunHBlank() { if (lcdClock >= 51) // Horizontal Blank lasts for ~51 m-clock ticks { lcdClock -= 51; LY++; // Move to next line // If LY == LYC, set the flag and raise interrupt if it's enabled matchFlag = LY == LYC; if (matchFlag && (enabledInterrupts & GPUInterrupts.OnMatch) != 0) { LCDCInterrupt?.Invoke(); } if (LY == 144) // Switch to vertical blank mode { // Begin rendering the frame renderTask = Task.Run(() => RenderFrame()); lcdMode = GPUMode.VBlank; // signal vertical blank interrupt and LCD interrupt (if enabled) VBlankInterrupt?.Invoke(); if ((enabledInterrupts & GPUInterrupts.OnVBlank) != 0) { LCDCInterrupt?.Invoke(); } } else // Start rendering the next line { lcdMode = GPUMode.OAMRead; if ((enabledInterrupts & GPUInterrupts.OnOAMRead) != 0) { LCDCInterrupt?.Invoke(); } renderTask = Task.Run(() => LoadSpriteData()); } } }
public void FrameStep() { clock.IncrementCycleCount(cpuClock.LastCycleCountIncrement); switch (gpuMode) { case GPUMode.HBlankPeriod: { if (clock.CycleCount >= hBlankCycleDuration) { clock.Reset(); gpuRegisters.CurrentScanLine++; if (gpuRegisters.CurrentScanLine == lcdLinesCount) { gpuMode = GPUMode.VBlankPeriod; //TODO draw image bmp.Save("D:\\test.bmp"); } else { gpuMode = GPUMode.OAMReadMode; } } } break; case GPUMode.VBlankPeriod: { if (clock.CycleCount >= vBlankCycleDuration) { clock.Reset(); gpuRegisters.CurrentScanLine++; if (gpuRegisters.CurrentScanLine > 153) { gpuMode = GPUMode.OAMReadMode; gpuRegisters.CurrentScanLine = 0; } } } break; case GPUMode.OAMReadMode: { if (clock.CycleCount >= oamRadModeCycleDuration) { gpuMode = GPUMode.VRAMReadMode; clock.Reset(); } } break; case GPUMode.VRAMReadMode: { if (clock.CycleCount >= vRadModeCycleDuration) { gpuMode = GPUMode.HBlankPeriod; clock.Reset(); RenderScan(); } } break; } }
public void TestGPUVBlankTiming() { CPU cpu = new CPU(new GPU()); MMU mmu = new MMU(); var rom = new ROM(); cpu.GPU.LCDC = LCDC.LCDEnable; decimal timeSpentMicroSec = 0; // Wait for the first VBlank while (cpu.GPU.Mode != GPUMode.Mode1) { cpu.Exec(0x00); if (cpu.InstructionsCount > 100000) { Assert.Fail("GPU did not changed to correct state"); } } GPUMode gpuMode = cpu.GPU.Mode; var mode1ClockCount = 0; while (true) { cpu.Exec(0x00); timeSpentMicroSec += (decimal)1.0 / cpu.ClockFrequencyMhz * cpu.LastInstructionClockTime; mode1ClockCount += cpu.LastInstructionClockTime; var newMode = cpu.GPU.Mode; if (gpuMode != newMode) { if (newMode == GPUMode.Mode1) { Assert.AreEqual(70224, mode1ClockCount); Assert.IsTrue(timeSpentMicroSec > 16700M); Assert.IsTrue(timeSpentMicroSec < 16800M); // VBlank frequency is around 59.73Hz var vsync = 1 / (double)timeSpentMicroSec * 1000.0 * 1000.0; Assert.IsTrue(vsync > 59.7); Assert.IsTrue(vsync < 59.8); return; } else if (newMode == GPUMode.Mode2 && gpuMode == GPUMode.Mode1) { Assert.AreEqual(4560, mode1ClockCount); // Mode 1 should stay up for 1.08msec Assert.IsTrue(timeSpentMicroSec > 1080M); Assert.IsTrue(timeSpentMicroSec < 1090M); } gpuMode = newMode; } if (cpu.InstructionsCount > 1000000) { Assert.Fail("GPU did not changed to correct state"); } } }
public GPU() { Mode = GPUMode.Mode1; }