2024-08-19 10:21:24 -05:00
|
|
|
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)
|
|
|
|
|
2024-08-19 21:05:08 -05:00
|
|
|
var scanline: UInt16 = 0
|
|
|
|
var cycles: Int = 0
|
|
|
|
|
2024-08-20 22:36:04 -05:00
|
|
|
var nmiInterrupt: UInt8? = nil
|
2024-08-19 21:05:08 -05:00
|
|
|
|
2024-08-19 10:21:24 -05:00
|
|
|
init(_ chrRom: [UInt8], _ mirroring: Mirroring) {
|
|
|
|
self.chrRom = chrRom
|
|
|
|
self.mirroring = mirroring
|
|
|
|
}
|
|
|
|
|
2024-08-19 21:05:08 -05:00
|
|
|
func tick(_ cycles: UInt8) -> Bool {
|
|
|
|
self.cycles += Int(cycles)
|
|
|
|
if self.cycles >= 341 {
|
2024-08-23 22:36:25 -05:00
|
|
|
if checkSprite0Hit(self.cycles) {
|
|
|
|
status.setSpriteZeroHit(true)
|
|
|
|
}
|
2024-08-19 21:05:08 -05:00
|
|
|
self.cycles = self.cycles - 341
|
2024-08-20 16:21:52 -05:00
|
|
|
self.scanline += 1
|
2024-08-19 21:05:08 -05:00
|
|
|
|
|
|
|
if scanline == 241 {
|
2024-08-20 16:21:52 -05:00
|
|
|
self.status.setVblankStatus(true)
|
|
|
|
status.setSpriteZeroHit(false)
|
|
|
|
if ctrl.generateVblankNMI() {
|
|
|
|
nmiInterrupt = 1
|
2024-08-19 21:05:08 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if scanline >= 262 {
|
|
|
|
scanline = 0
|
|
|
|
nmiInterrupt = nil
|
|
|
|
status.setSpriteZeroHit(false)
|
|
|
|
self.status.resetVblankStatus()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-08-23 22:36:25 -05:00
|
|
|
func checkSprite0Hit(_ cycle: Int) -> Bool {
|
|
|
|
let y = Int(oamData[0])
|
|
|
|
let x = Int(oamData[0])
|
|
|
|
return (y == scanline) && x <= cycle && mask.showSprites()
|
|
|
|
}
|
|
|
|
|
2024-08-20 22:36:04 -05:00
|
|
|
func pollNMI() -> UInt8? {
|
|
|
|
let tmp = self.nmiInterrupt
|
|
|
|
self.nmiInterrupt = nil
|
|
|
|
return tmp
|
|
|
|
}
|
|
|
|
|
2024-08-19 10:21:24 -05:00
|
|
|
func writeToPPUAddr(_ value: UInt8) {
|
|
|
|
addr.update(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
func writeToCtrl(_ value: UInt8) {
|
2024-08-19 21:05:08 -05:00
|
|
|
let beforeNmiStatus = ctrl.generateVblankNMI()
|
2024-08-19 10:21:24 -05:00
|
|
|
ctrl.rawValue = value
|
2024-08-19 21:05:08 -05:00
|
|
|
if !beforeNmiStatus && ctrl.generateVblankNMI() && status.isInVblank() {
|
|
|
|
nmiInterrupt = 1
|
|
|
|
}
|
2024-08-19 10:21:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2024-08-20 16:21:52 -05:00
|
|
|
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
|
|
|
|
}
|
2024-08-19 10:21:24 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|