public PpuMemoryRegisters(IPpu ppu) { this.ppu = ppu; LCDC = new LcdControlRegister(ppu); STAT = new LcdStatusRegister(ppu); }
// We clock our CPU at 1mhz so we use MCycles // The GPU state machine works ayncronously to the CPU (in HW but not in the emulator). It works like this, measured in CPU cycles: // [OAM Search 20 cycles] -> [Pixel Transfer 43 cycles] -> [HBlank 51] = 114 clocks per line // [VBlank 1140 cycles] // 144 lines on screen + 10 lines vblank = 154 lines // 114 * 154 = 17,556 clocks per screen // 1,048,576 / 17,556 = 59.72hz refresh rate public void Step() { UInt32 cpuTickCount = dmg.cpu.Ticks; // Here we monitor how many cycles the CPU has executed and we map the GPU state to match how the real hardware behaves. This allows // us to generate interupts at the right time if (MemoryRegisters.LCDC.LcdEnable == 0) { return; } UInt32 tickCount = (cpuTickCount - lastCpuTickCount); // Track how many cycles the CPU has done since we last changed states elapsedTicks += tickCount; lastCpuTickCount = cpuTickCount; LcdStatusRegister lcdStat = MemoryRegisters.STAT; switch (Mode) { // See the enable function for some details on this case PpuMode.Glitched_OAM: if (elapsedTicks >= Glitched_OAM_Length) { Mode = PpuMode.PixelTransfer; elapsedTicks -= Glitched_OAM_Length; } break; case PpuMode.OamSearch: if (elapsedTicks >= OAM_Length) { OamSearch(); Mode = PpuMode.PixelTransfer; elapsedTicks -= OAM_Length; } break; // Transfers one scanline of pixels to the screen // The emulator must also work this way as the cpu is still running and many graphical effects change the state of the system between scanlines case PpuMode.PixelTransfer: if (elapsedTicks >= PixelTransfer_Length) { RenderScanline(); Mode = PpuMode.HBlank; if (lcdStat.HBlankInterruptEnable) { dmg.interrupts.RequestInterrupt(Interrupts.Interrupt.INTERRUPTS_LCDSTAT); } elapsedTicks -= PixelTransfer_Length; } break; // PPU is idle during hblank case PpuMode.HBlank: // 51 machine cycles ticks (mcycles) if (elapsedTicks >= HBlank_Length) { CurrentScanline++; // Coincidence interrupt checks if the scanline is equal to the value in FF45 if (lcdStat.LycLyCoincidenceInterruptEnable && CurrentScanline == lcdStat.LYC) { dmg.interrupts.RequestInterrupt(Interrupts.Interrupt.INTERRUPTS_LCDSTAT); } if (CurrentScanline == 144) { Mode = PpuMode.VBlank; vblankTicks = (elapsedTicks - HBlank_Length); //This will only fire the interrupt if IE for vblank is set dmg.interrupts.RequestInterrupt(Interrupts.Interrupt.INTERRUPTS_VBLANK); if (lcdStat.VBlankInterruptEnable) { dmg.interrupts.RequestInterrupt(Interrupts.Interrupt.INTERRUPTS_LCDSTAT); } // We can set the renderer drawing the frame as soon as we enter vblank lock (FrameBuffer) { // Flip frames if (FrameBuffer == frameBuffer0) { FrameBuffer = frameBuffer1; drawBuffer = frameBuffer0; } else { FrameBuffer = frameBuffer0; drawBuffer = frameBuffer1; } } // lock to 60fps double fps60 = 1000 / 60.0; while (dmg.EmulatorTimer.Elapsed.TotalMilliseconds - lastFrameTime < fps60) { } lastFrameTime = dmg.EmulatorTimer.Elapsed.TotalMilliseconds; if (dmg.OnFrame != null) { dmg.OnFrame(); } } else { Mode = PpuMode.OamSearch; if (lcdStat.OamInterruptEnable) { dmg.interrupts.RequestInterrupt(Interrupts.Interrupt.INTERRUPTS_LCDSTAT); } } // Don't lose any ticks, cannot set to zero elapsedTicks -= HBlank_Length; } break; // PPU is idle during vblank // The vblank takes the equivilant of 10 scanlines case PpuMode.VBlank: vblankTicks += tickCount; if (elapsedTicks >= ScanLine_Length) { CurrentScanline++; // Coincidence interrupt checks if the scanline is equal to the value in FF45 if (lcdStat.LycLyCoincidenceInterruptEnable && CurrentScanline == lcdStat.LYC) { dmg.interrupts.RequestInterrupt(Interrupts.Interrupt.INTERRUPTS_LCDSTAT); } elapsedTicks -= ScanLine_Length; } if (CurrentScanline == 154) { CurrentScanline = 0; // Coincidence interrupt checks if the scanline is equal to the value in FF45 if (lcdStat.LycLyCoincidenceInterruptEnable && CurrentScanline == lcdStat.LYC) { dmg.interrupts.RequestInterrupt(Interrupts.Interrupt.INTERRUPTS_LCDSTAT); } if ((vblankTicks - elapsedTicks) != VBlank_Length) { throw new Exception("PPU out of sync"); } Mode = PpuMode.OamSearch; // Debugger hooks if (dmg.OnFrameEnd != null) { dmg.OnFrameEnd(frame, false); } frame++; // Debugger hooks if (dmg.OnFrameStart != null) { dmg.OnFrameStart(frame); } } break; } }