public PPU(Cpu cpu, Cartridge cartridge, IDisplay display, Nes nes) { _nes = nes; _display = display; _cpu = cpu; _cartridge = cartridge; for (int i = 0; i < 64; i++) { Oam[i] = new Oam(); } }
public void Clock() { if (_scanline >= -1 && _scanline < 240) { if (_scanline == 0 && _cycle == 0) { _cycle = 1; } else if (_scanline == -1 && _cycle == 1) { PPURegisters.PPUSTATUS.VerticalBlank = false; // Clear Shifters for (int i = 0; i < 8; i++) { _spriteShifterPatternLow[i] = 0; _spriteShifterPatternHigh[i] = 0; PPURegisters.PPUSTATUS.SpriteOverflow = false; PPURegisters.PPUSTATUS.SpriteHit = false; } } else if ((_cycle >= 2 && _cycle < 258) || (_cycle >= 321 && _cycle < 338)) { UpdateShifters(); switch ((_cycle - 1) % 8) { case 0: LoadBgShifters(); _bgNextTileId = ReadPpu(NAMETABLE | (_vram.Value & 0x0FFF)); break; case 2: _bgNextTileAttr = ReadPpu(NAMETABLE_ATTRIBUTES | (_vram.NametableY << 11) | (_vram.NametableX << 10) | ((_vram.CoarseY >> 2) << 3) | (_vram.CoarseX >> 2)); if ((_vram.CoarseY & 0x02) != 0) { _bgNextTileAttr >>= 4; } if ((_vram.CoarseX & 0x02) != 0) { _bgNextTileAttr >>= 2; } _bgNextTileAttr &= 0x03; break; case 4: _bgNextTileLo = ReadPpu(((PPURegisters.PPUCTRL.PatternBackground ? 1 : 0) << 12) + (_bgNextTileId << 4) + _vram.FineY); break; case 6: _bgNextTileHi = ReadPpu(((PPURegisters.PPUCTRL.PatternBackground ? 1 : 0) << 12) + (_bgNextTileId << 4) + (_vram.FineY) + 8); break; case 7: IncrementScrollX(); break; default: break; } } if (_cycle == 256) { IncrementScrollY(); } else if (_cycle == 257) { LoadBgShifters(); TransferAddressX(); } else if (_cycle == 338 || _cycle == 340) { _bgNextTileId = ReadPpu(NAMETABLE | (_vram.Value & 0x0FFF)); } else if (_scanline == -1 && _cycle >= 280 && _cycle < 305) { TransferAddressY(); } if (_cycle == 257 && _scanline >= 0) { for (int i = 0; i < 8; i++) { _spriteScanline[i] = new Oam(); } _spriteCount = 0; for (int i = 0; i < 8; i++) { _spriteShifterPatternLow[i] = 0; _spriteShifterPatternHigh[i] = 0; } byte index = 0; _spriteZeroHitPossible = false; while (index < 64 && _spriteCount < 9) { int diff = _scanline - Oam[index].Y; if (diff >= 0 && diff < (PPURegisters.PPUCTRL.SpriteHeight ? 16 : 8)) { if (_spriteCount < 8) { if (index == 0) { _spriteZeroHitPossible = true; } _spriteScanline[_spriteCount] = Oam[index]; _spriteCount++; } } index++; } PPURegisters.PPUSTATUS.SpriteOverflow = (_spriteCount > 8); } if (_cycle == 340) { for (byte i = 0; i < _spriteCount; i++) { byte spritePatternBitsLo, spritePatternBitsHi; ushort spritePatternAddrLo, spritePatternAddrHi; if (!PPURegisters.PPUCTRL.SpriteHeight) { if ((_spriteScanline[i].Attributes & 0x80) == 0) { spritePatternAddrLo = (ushort)( ((PPURegisters.PPUCTRL.PatternSprite ? 1 : 0) << 12) | (_spriteScanline[i].Id << 4) | (_scanline - _spriteScanline[i].Y)); } else { spritePatternAddrLo = (ushort)( ((PPURegisters.PPUCTRL.PatternSprite ? 1 : 0) << 12) | (_spriteScanline[i].Id << 4) | (7 - (_scanline - _spriteScanline[i].Y))); } } else { if ((_spriteScanline[i].Attributes & 0x80) == 0) { if (_scanline - _spriteScanline[i].Y < 8) { spritePatternAddrLo = (ushort)( ((_spriteScanline[i].Id & 0x01) << 12) | ((_spriteScanline[i].Id & 0xFE) << 4) | ((_scanline - _spriteScanline[i].Y) & 0x07)); } else { spritePatternAddrLo = (ushort)( ((_spriteScanline[i].Id & 0x01) << 12) | (((_spriteScanline[i].Id & 0xFE) + 1) << 4) | ((_scanline - _spriteScanline[i].Y) & 0x07)); } } else { if (_scanline - _spriteScanline[i].Y < 8) { spritePatternAddrLo = (ushort)( ((_spriteScanline[i].Id & 0x01) << 12) | (((_spriteScanline[i].Id & 0xFE) + 1) << 4) | (7 - (_scanline - _spriteScanline[i].Y) & 0x07)); } else { spritePatternAddrLo = (ushort)(((_spriteScanline[i].Id & 0x01) << 12) | ((_spriteScanline[i].Id & 0xFE) << 4) | (7 - (_scanline - _spriteScanline[i].Y) & 0x07)); } } } spritePatternAddrHi = (ushort)(spritePatternAddrLo + 8); spritePatternBitsLo = ReadPpu(spritePatternAddrLo); spritePatternBitsHi = ReadPpu(spritePatternAddrHi); if ((_spriteScanline[i].Attributes & 0x40) > 0) { spritePatternBitsLo = FlipByte(spritePatternBitsLo); spritePatternBitsHi = FlipByte(spritePatternBitsHi); } _spriteShifterPatternLow[i] = spritePatternBitsLo; _spriteShifterPatternHigh[i] = spritePatternBitsHi; } } } else if (_scanline == 240) { //do nothing } else if (_scanline == 241 && _cycle == 1) { //First pixel of the nonvisible scanline PPURegisters.PPUSTATUS.VerticalBlank = true; if (PPURegisters.PPUCTRL.NmiEnabled) { _cpu.Nmi(); } } if (_cycle > 0 && _scanline >= 0 && _cycle - 1 < 256 && _scanline < 240) { byte bg_pixel = 0x00; byte bg_palette = 0x00; if (PPURegisters.PPUMASK.BackgroundEnable) { var bit_mux = (ushort)(0x8000 >> _fineX); var p0_pixel = (_bgShifterPatternLo & bit_mux) != 0 ? 1 : 0; var p1_pixel = (_bgShifterPatternHi & bit_mux) != 0 ? 1 : 0; bg_pixel = (byte)((p1_pixel << 1) | p0_pixel); var bg_pal0 = (_bgShifterAttributeLo & bit_mux) != 0 ? 1 : 0; var bg_pal1 = (_bgShifterAttributeHi & bit_mux) != 0 ? 1 : 0; bg_palette = (byte)((bg_pal1 << 1) | bg_pal0); } byte fg_pixel = 0x00; byte fg_palette = 0x00; byte fg_priority = 0x00; if (PPURegisters.PPUMASK.SpriteEnable) { _spriteZeroBeingRendered = false; for (byte i = 0; i < _spriteCount; i++) { if (_spriteScanline[i].X == 0) { byte fg_pixel_lo = (byte)((_spriteShifterPatternLow[i] & 0x80) > 0 ? 1 : 0); byte fg_pixel_hi = (byte)((_spriteShifterPatternHigh[i] & 0x80) > 0 ? 1 : 0); fg_pixel = (byte)((fg_pixel_hi << 1) | fg_pixel_lo); fg_palette = (byte)((_spriteScanline[i].Attributes & 0x03) + 0x04); fg_priority = (byte)((_spriteScanline[i].Attributes & 0x20) == 0 ? 1 : 0); if (fg_pixel != 0) { if (i == 0) { _spriteZeroBeingRendered = true; } break; } } } } byte pixel = 0x00; byte palette = 0x00; if (bg_pixel == 0 && fg_pixel == 0) { pixel = 0x00; palette = 0x00; } else if (bg_pixel == 0 && fg_pixel > 0) { pixel = fg_pixel; palette = fg_palette; } else if (bg_pixel > 0 && fg_pixel == 0) { pixel = bg_pixel; palette = bg_palette; } else if (bg_pixel > 0 && fg_pixel > 0) { if (fg_priority == 1) { pixel = fg_pixel; palette = fg_palette; } else { pixel = bg_pixel; palette = bg_palette; } if (_spriteZeroHitPossible && _spriteZeroBeingRendered) { if (PPURegisters.PPUMASK.SpriteEnable && PPURegisters.PPUMASK.BackgroundEnable) { if (!(PPURegisters.PPUMASK.BackgroundLeftColumnEnable || PPURegisters.PPUMASK.SpriteLeftColumnEnable)) { if (_cycle >= 9 && _cycle < 258) { PPURegisters.PPUSTATUS.SpriteHit = true; } } else { if (_cycle >= 1 && _cycle < 258) { PPURegisters.PPUSTATUS.SpriteHit = true; } } } } } _display.DrawPixel(_cycle - 1, _scanline, GetColorFromPalette(palette, pixel)); if (_nes.SuperSlow > 0 && _cycle > 2 && _cycle < 256) { _display.DrawPixel((_cycle - 1 + 1) % 256, _scanline, 0xFF00fF); } } _cycle++; if (_cycle >= 341) { _cycle = 0; _scanline++; if (_scanline >= 261) { _scanline = -1; FrameFinished = true; _display.SetFrameFinished(); } } }