SwiftNES/Sources/PPU/NesPPU.swift
2024-08-23 23:36:25 -04:00

181 lines
5.0 KiB
Swift

class NesPPU {
public var paletteTable = [UInt8](repeating: 0, count: 32)
public var vram = [UInt8](repeating: 0, count: 2048)
public let mirroring: Mirroring
public var chrRom: [UInt8]
private let addr: AddrRegister = AddrRegister()
private var readBuff: UInt8 = 0
var ctrl = ControlRegister()
let status = StatusRegister()
let scroll = ScrollRegister()
let mask = MaskRegister()
var oamAddr: UInt8 = 0
var oamData = [UInt8](repeating: 0, count: 64 * 4)
var scanline: UInt16 = 0
var cycles: Int = 0
var nmiInterrupt: UInt8? = nil
init(_ chrRom: [UInt8], _ mirroring: Mirroring) {
self.chrRom = chrRom
self.mirroring = mirroring
}
func tick(_ cycles: UInt8) -> Bool {
self.cycles += Int(cycles)
if self.cycles >= 341 {
if checkSprite0Hit(self.cycles) {
status.setSpriteZeroHit(true)
}
self.cycles = self.cycles - 341
self.scanline += 1
if scanline == 241 {
self.status.setVblankStatus(true)
status.setSpriteZeroHit(false)
if ctrl.generateVblankNMI() {
nmiInterrupt = 1
}
}
if scanline >= 262 {
scanline = 0
nmiInterrupt = nil
status.setSpriteZeroHit(false)
self.status.resetVblankStatus()
return true
}
}
return false
}
func checkSprite0Hit(_ cycle: Int) -> Bool {
let y = Int(oamData[0])
let x = Int(oamData[0])
return (y == scanline) && x <= cycle && mask.showSprites()
}
func pollNMI() -> UInt8? {
let tmp = self.nmiInterrupt
self.nmiInterrupt = nil
return tmp
}
func writeToPPUAddr(_ value: UInt8) {
addr.update(value)
}
func writeToCtrl(_ value: UInt8) {
let beforeNmiStatus = ctrl.generateVblankNMI()
ctrl.rawValue = value
if !beforeNmiStatus && ctrl.generateVblankNMI() && status.isInVblank() {
nmiInterrupt = 1
}
}
func incrememtVramAddr() {
addr.increment(ctrl.vramAddrIncrement())
}
func readData() -> UInt8 {
let addr = addr.get()
incrememtVramAddr()
switch addr {
case 0...0x1fff:
let res = readBuff
readBuff = chrRom[Int(addr)]
return res
case 0x2000...0x2fff:
let res = readBuff
readBuff = vram[Int(mirrorVramAddr(addr))]
return res
case 0x3000...0x3eff:
fatalError("addr space 0x3000..0x3eff is not expected to be used, requested = \(addr)")
case 0x3f00...0x3fff:
return self.paletteTable[Int(addr - 0x3f00)]
default:
fatalError("Unexpected access to mirrored space \(addr)")
}
}
func mirrorVramAddr(_ addr: UInt16) -> UInt16 {
let mirroredVram = addr & 0b10111111111111 // mirror down 0x3000-0x3eff to 0x2000 - 0x2eff
let vramIndex = mirroredVram - 0x2000 // to vram array index
let nameTable = vramIndex / 0x400 // to the name index table
return switch (mirroring, nameTable) {
case (.vertical, 2), (.vertical, 3):
vramIndex - 0x800
case (.horizontal, 2):
vramIndex - 0x400
case (.horizontal, 1):
vramIndex - 0x400
case (.horizontal, 3):
vramIndex - 0x800
default:
vramIndex
}
}
func writeToData(_ data: UInt8) {
let addr = addr.get()
switch addr {
case 0...0x1fff:
print("Attempt to write to chr rom space \(addr)!")
case 0x2000...0x2fff:
self.vram[Int(mirrorVramAddr(addr))] = data
case 0x3000...0x3eff:
fatalError("addr \(addr) should not be used in reality!")
//Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C
case 0x3f10, 0x3f14, 0x3f18, 0x3f1c:
let addrMirror = addr - 0x10
paletteTable[Int(addrMirror - 0x3f00)] = data
case 0x3f00...0x3fff:
paletteTable[Int(addr - 0x3f00)] = data
default:
fatalError("Unexpected access to mirrored space \(addr)")
}
incrememtVramAddr()
}
func writeOamDma(_ buffer: [UInt8]) {
for x in buffer {
oamData[Int(oamAddr)] = x
oamAddr = oamAddr &+ 1
}
}
func readStatus() -> UInt8 {
let data = status.snapshot()
status.resetVblankStatus()
addr.resetLatch()
scroll.resetLatch()
return data
}
func writeToScroll(_ value: UInt8) {
scroll.write(value)
}
func writeToOamAddr(_ value: UInt8) {
oamAddr = value
}
func writeToOamData(_ value: UInt8) {
oamData[Int(oamAddr)] = value
oamAddr = oamAddr &+ 1
}
func readOamData() -> UInt8 {
oamData[Int(oamAddr)]
}
func writeToMask(_ value: UInt8) {
mask.update(value)
}
}