Compare commits

..

3 Commits

Author SHA1 Message Date
Andrew Glaze
f9efe23b0b PPU: background rendering 2024-08-20 23:36:04 -04:00
Andrew Glaze
be170bf750 remove large log file 2024-08-20 17:22:53 -04:00
1917d0f82e
PPU: work on rendering 2024-08-20 17:21:52 -04:00
7 changed files with 163 additions and 23 deletions

View File

@ -131,6 +131,7 @@ class CPU {
status = [.interruptDisable, .break2] status = [.interruptDisable, .break2]
programCounter = self.memReadU16(0xFFFC) programCounter = self.memReadU16(0xFFFC)
print(programCounter)
} }
//func loadAndRun(_ program: [UInt8]) { //func loadAndRun(_ program: [UInt8]) {
@ -150,14 +151,16 @@ class CPU {
//} //}
func run() { func run() {
run(onCycle: {}, onComplete: {}) run(onCycle: { print(dumpCpuState(self)) }, onComplete: {})
} }
func run(onCycle: @escaping () -> (), onComplete: @escaping () -> ()) { func run(onCycle: @escaping () -> (), onComplete: @escaping () -> ()) {
let opcodes = OPCODES_MAP let opcodes = OPCODES_MAP
while true { while true {
if let _nmi = bus.pollNMI() { if bus.pollNMI() != nil {
print(programCounter)
interrupt(.NMI) interrupt(.NMI)
print(programCounter)
} }
processOpcodes(onCycle: onCycle, opcodes: opcodes) { processOpcodes(onCycle: onCycle, opcodes: opcodes) {
onComplete() onComplete()

View File

@ -9,7 +9,7 @@ func dumpCpuState(_ cpu: CPU) -> String {
{ {
let (addr, _) = cpu.getAbsoluteAddress(opcode.mode, addr: cpu.programCounter + 1) let (addr, _) = cpu.getAbsoluteAddress(opcode.mode, addr: cpu.programCounter + 1)
return (addr, cpu.memRead(addr)) return (addr, 0)//cpu.memRead(addr))
}() }()
} }

View File

@ -3,6 +3,7 @@ class Bus {
var prgRom: [UInt8] var prgRom: [UInt8]
var ppu: NesPPU var ppu: NesPPU
var cycles: Int = 0 var cycles: Int = 0
var gameloopCallback: (NesPPU) -> ()
fileprivate let RAM : UInt16 = 0x0000 fileprivate let RAM : UInt16 = 0x0000
fileprivate let RAM_MIRRORS_END: UInt16 = 0x1FFF fileprivate let RAM_MIRRORS_END: UInt16 = 0x1FFF
@ -11,18 +12,27 @@ class Bus {
fileprivate let ROM_ADDRESS_START: UInt16 = 0x8000 fileprivate let ROM_ADDRESS_START: UInt16 = 0x8000
fileprivate let ROM_ADDRESS_END: UInt16 = 0xFFFF fileprivate let ROM_ADDRESS_END: UInt16 = 0xFFFF
init(_ rom: Rom) { init(_ rom: Rom, gameloopCallback: @escaping (NesPPU) -> ()) {
ppu = NesPPU(rom.character, rom.screenMirror) ppu = NesPPU(rom.character, rom.screenMirror)
self.prgRom = rom.program self.prgRom = rom.program
self.gameloopCallback = gameloopCallback
} }
func tick(_ cycles: UInt8) { func tick(_ cycles: UInt8) {
self.cycles += Int(cycles) self.cycles += Int(cycles)
let nmiBefore = ppu.nmiInterrupt != nil
//print(nmiBefore)
self.ppu.tick(cycles * 3) self.ppu.tick(cycles * 3)
let nmiAfter = ppu.nmiInterrupt != nil
//print(nmiAfter)
if !nmiBefore && nmiAfter {
gameloopCallback(ppu)
}
} }
func pollNMI() -> UInt8? { func pollNMI() -> UInt8? {
ppu.nmiInterrupt ppu.pollNMI()
} }
} }
@ -34,13 +44,20 @@ extension Bus: Memory {
let mirrorDownAddr = addr & 0b00000111_11111111 let mirrorDownAddr = addr & 0b00000111_11111111
return self.cpuVram[Int(mirrorDownAddr)] return self.cpuVram[Int(mirrorDownAddr)]
case 0x2000, 0x2001, 0x2003, 0x2005, 0x2006, 0x4014: case 0x2000, 0x2001, 0x2003, 0x2005, 0x2006, 0x4014:
fatalError("Attempt to read from write-only PPU address \(addr)") //fatalError("Attempt to read from write-only PPU address \(addr)")
return 0
case 0x2002: case 0x2002:
return ppu.readStatus() return ppu.readStatus()
case 0x2004: case 0x2004:
return ppu.readOamData() return ppu.readOamData()
case 0x2007: case 0x2007:
return ppu.readData() return ppu.readData()
case 0x4000...0x4015:
return 0 // Ignore APU
case 0x4016:
return 0 // Ignore Joy 1
case 0x4017:
return 0 // Ignore Joy 2
case 0x2008...PPU_REGISTERS_MIRRORS_END: case 0x2008...PPU_REGISTERS_MIRRORS_END:
let mirrorDownAddr = addr & 0b00100000_00000111; let mirrorDownAddr = addr & 0b00100000_00000111;
return self.memRead(mirrorDownAddr) return self.memRead(mirrorDownAddr)
@ -76,6 +93,20 @@ extension Bus: Memory {
case 0x2008...PPU_REGISTERS_MIRRORS_END: case 0x2008...PPU_REGISTERS_MIRRORS_END:
let mirrorDownAddr = addr & 0b00100000_00000111 let mirrorDownAddr = addr & 0b00100000_00000111
memWrite(mirrorDownAddr, data: data) memWrite(mirrorDownAddr, data: data)
case 0x4000...0x4013, 0x4015:
return // Ignore APU
case 0x4016:
return // ignore Joy 1
case 0x4017:
return // Ignore Joy 2
case 0x4014:
var buffer = [UInt8](repeating: 0, count: 256)
let hi = UInt16(data) << 8
for i in 0..<256 {
buffer[i] = memRead(hi + UInt16(i))
}
ppu.writeOamDma(buffer)
case ROM_ADDRESS_START...ROM_ADDRESS_END: case ROM_ADDRESS_START...ROM_ADDRESS_END:
fatalError("Attempt to write to Cartridge ROM space: \(addr)") fatalError("Attempt to write to Cartridge ROM space: \(addr)")
default: default:

View File

@ -18,7 +18,7 @@ class NesPPU {
var scanline: UInt16 = 0 var scanline: UInt16 = 0
var cycles: Int = 0 var cycles: Int = 0
var nmiInterrupt: UInt8? var nmiInterrupt: UInt8? = nil
init(_ chrRom: [UInt8], _ mirroring: Mirroring) { init(_ chrRom: [UInt8], _ mirroring: Mirroring) {
self.chrRom = chrRom self.chrRom = chrRom
@ -28,16 +28,16 @@ class NesPPU {
func tick(_ cycles: UInt8) -> Bool { func tick(_ cycles: UInt8) -> Bool {
self.cycles += Int(cycles) self.cycles += Int(cycles)
if self.cycles >= 341 { if self.cycles >= 341 {
//print(self.cycles)
self.cycles = self.cycles - 341 self.cycles = self.cycles - 341
scanline += 1 self.scanline += 1
if scanline == 241 { if scanline == 241 {
if self.ctrl.generateVblankNMI() {
self.status.setVblankStatus(true) self.status.setVblankStatus(true)
status.setSpriteZeroHit(false) status.setSpriteZeroHit(false)
if ctrl.generateVblankNMI() { if ctrl.generateVblankNMI() {
nmiInterrupt = 1 nmiInterrupt = 1
} print("interrupt")
} }
} }
@ -52,6 +52,12 @@ class NesPPU {
return false return false
} }
func pollNMI() -> UInt8? {
let tmp = self.nmiInterrupt
self.nmiInterrupt = nil
return tmp
}
func writeToPPUAddr(_ value: UInt8) { func writeToPPUAddr(_ value: UInt8) {
addr.update(value) addr.update(value)
} }
@ -109,7 +115,32 @@ class NesPPU {
} }
func writeToData(_ data: UInt8) { func writeToData(_ data: UInt8) {
fatalError("Not Implemented") let addr = addr.get()
print("\(addr): \(data)")
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 { func readStatus() -> UInt8 {

View File

@ -28,7 +28,7 @@ struct ControlRegister: OptionSet {
static let GENERATE_NMI = ControlRegister(rawValue: 0b10000000) static let GENERATE_NMI = ControlRegister(rawValue: 0b10000000)
func vramAddrIncrement() -> UInt8 { func vramAddrIncrement() -> UInt8 {
if self.contains(.VRAM_ADD_INCREMENT) { if !self.contains(.VRAM_ADD_INCREMENT) {
1 1
} else { } else {
32 32
@ -38,4 +38,12 @@ struct ControlRegister: OptionSet {
func generateVblankNMI() -> Bool { func generateVblankNMI() -> Bool {
self.contains(.GENERATE_NMI) self.contains(.GENERATE_NMI)
} }
func backgroundPatternAddr() -> Int {
if !self.contains(.BACKROUND_PATTERN_ADDR) {
0
} else {
0x1000
}
}
} }

View File

@ -0,0 +1,37 @@
class Render {
static func render(_ ppu: NesPPU, frame: Frame) {
let bank = ppu.ctrl.backgroundPatternAddr()
for i in 0..<0x03c0 { // For now, just use first nametable
let tileAddr = UInt16(ppu.vram[i])
//print(ppu.vram)
let tileX = i % 32
let tileY = i / 32
let tile = ppu.chrRom[(bank + Int(tileAddr) * 16)...(bank + Int(tileAddr) * 16 + 15)]
for y in 0...7 {
var upper = tile[tile.startIndex + y]
var lower = tile[tile.startIndex + y + 8]
for x in (0...7).reversed() {
let value = (1 & upper) << 1 | (1 & lower)
upper = upper >> 1
lower = lower >> 1
let rgb = switch value {
case 0:
NESColor.SYSTEM_PALLETE[0x01]
case 1:
NESColor.SYSTEM_PALLETE[0x23]
case 2:
NESColor.SYSTEM_PALLETE[0x28]
case 3:
NESColor.SYSTEM_PALLETE[0x31]
default:
fatalError("Invalid Pallete Color type")
}
frame.setPixel((tileX * 8 + x, tileY * 8 + y), rgb)
}
}
}
}
}

View File

@ -100,14 +100,13 @@ bytes.getBytes(&gameCode, length: bytes.length)
let rom = try Rom(gameCode) let rom = try Rom(gameCode)
//let tileFrame = TileViewer.showTile(chrRom: rom.character, bank: 1, tileNum: 0) var frame = Frame()
let tileFrame = TileViewer.showTileBank(chrRom: rom.character, bank: 1) let bus = Bus(rom) { ppu in
Render.render(ppu, frame: frame)
SDL_UpdateTexture(texture, nil, tileFrame.data, 256 * 3) SDL_UpdateTexture(texture, nil, frame.data, 256 * 3)
SDL_RenderCopy(canvas, texture, nil, nil) SDL_RenderCopy(canvas, texture, nil, nil)
SDL_RenderPresent(canvas) SDL_RenderPresent(canvas)
while true {
while SDL_PollEvent(&event) > 0 { while SDL_PollEvent(&event) > 0 {
if event.type == SDL_QUIT.rawValue { if event.type == SDL_QUIT.rawValue {
SDL_DestroyWindow(window) SDL_DestroyWindow(window)
@ -127,6 +126,37 @@ while true {
} }
} }
let cpu = CPU(bus: bus)
cpu.reset()
cpu.run()
// let tileFrame = TileViewer.showTile(chrRom: rom.character, bank: 1, tileNum: 0)
// let tileFrame = TileViewer.showTileBank(chrRom: rom.character, bank: 1)
// SDL_UpdateTexture(texture, nil, tileFrame.data, 256 * 3)
// SDL_RenderCopy(canvas, texture, nil, nil)
// SDL_RenderPresent(canvas)
// while true {
// while SDL_PollEvent(&event) > 0 {
// if event.type == SDL_QUIT.rawValue {
// SDL_DestroyWindow(window)
// SDL_Quit()
// exit(0)
// }
// if event.type == SDL_KEYDOWN.rawValue {
// switch SDL_KeyCode(UInt32(event.key.keysym.sym)) {
// case SDLK_ESCAPE:
// SDL_DestroyWindow(window)
// SDL_Quit()
// exit(0)
// default:
// continue
// }
// }
// }
// }
// let bus = Bus(try! Rom(gameCode)) // let bus = Bus(try! Rom(gameCode))
//var cpu = CPU(bus: bus) //var cpu = CPU(bus: bus)