示例#1
0
 public PpuMemoryRegisters(IPpu ppu)
 {
     this.ppu = ppu;
     LCDC     = new LcdControlRegister(ppu);
     STAT     = new LcdStatusRegister(ppu);
 }
示例#2
0
        // 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;
            }
        }