Compare commits

...

2 Commits

Author SHA1 Message Date
Andrew Glaze
6974110b22 Render: Impl sprite rendering 2024-08-21 16:42:05 -04:00
Andrew Glaze
ddd8fd6ee6 PPU: Impl color palettes 2024-08-21 10:13:15 -04:00
4 changed files with 116 additions and 18 deletions

View File

@ -131,7 +131,6 @@ 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]) {
@ -151,16 +150,14 @@ class CPU {
//} //}
func run() { func run() {
run(onCycle: { print(dumpCpuState(self)) }, onComplete: {}) run(onCycle: {}, 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 bus.pollNMI() != nil { if bus.pollNMI() != nil {
print(programCounter)
interrupt(.NMI) interrupt(.NMI)
print(programCounter)
} }
processOpcodes(onCycle: onCycle, opcodes: opcodes) { processOpcodes(onCycle: onCycle, opcodes: opcodes) {
onComplete() onComplete()
@ -197,7 +194,6 @@ class CPU {
let programCounterState = programCounter let programCounterState = programCounter
guard let opcode = opcodes[code] else {fatalError("OpCode \(code) not recgonized!")} guard let opcode = opcodes[code] else {fatalError("OpCode \(code) not recgonized!")}
// print(programCounter, opcode.mnemonic)
switch code { switch code {
/// LDA /// LDA

View File

@ -28,7 +28,6 @@ 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
self.scanline += 1 self.scanline += 1
@ -37,7 +36,6 @@ class NesPPU {
status.setSpriteZeroHit(false) status.setSpriteZeroHit(false)
if ctrl.generateVblankNMI() { if ctrl.generateVblankNMI() {
nmiInterrupt = 1 nmiInterrupt = 1
print("interrupt")
} }
} }
@ -116,7 +114,6 @@ class NesPPU {
func writeToData(_ data: UInt8) { func writeToData(_ data: UInt8) {
let addr = addr.get() let addr = addr.get()
print("\(addr): \(data)")
switch addr { switch addr {
case 0...0x1fff: case 0...0x1fff:
print("Attempt to write to chr rom space \(addr)!") print("Attempt to write to chr rom space \(addr)!")

View File

@ -46,4 +46,28 @@ struct ControlRegister: OptionSet {
0x1000 0x1000
} }
} }
func spritePatternAddr() -> Int {
if !self.contains(.SPRITE_PATTERN_ADDR) {
0
} else {
0x1000
}
}
func spriteSize() -> Int {
if !self.contains(.SPRITE_SIZE) {
8
} else {
16
}
}
func masterSlaveSelect() -> Int {
if !self.contains(.SPRITE_SIZE) {
0
} else {
1
}
}
} }

View File

@ -2,36 +2,117 @@ class Render {
static func render(_ ppu: NesPPU, frame: Frame) { static func render(_ ppu: NesPPU, frame: Frame) {
let bank = ppu.ctrl.backgroundPatternAddr() let bank = ppu.ctrl.backgroundPatternAddr()
for i in 0..<0x03c0 { // For now, just use first nametable for i in 0..<0x03c0 { // FIXME: For now, just use first nametable
let tileAddr = UInt16(ppu.vram[i]) let tileAddr = UInt16(ppu.vram[i])
//print(ppu.vram) let tileLoc = (col: i % 32, row: i / 32)
let tileX = i % 32
let tileY = i / 32
let tile = ppu.chrRom[(bank + Int(tileAddr) * 16)...(bank + Int(tileAddr) * 16 + 15)] let tile = ppu.chrRom[(bank + Int(tileAddr) * 16)...(bank + Int(tileAddr) * 16 + 15)]
let bgPalette = getBgPalette(ppu, tileLoc: tileLoc)
// MARK: Draw Background
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 & lower) << 1 | (1 & upper)
upper = upper >> 1
lower = lower >> 1
let rgb = switch value {
case 0:
NESColor.SYSTEM_PALLETE[Int(ppu.paletteTable[0])]
case 1:
NESColor.SYSTEM_PALLETE[Int(bgPalette[1])]
case 2:
NESColor.SYSTEM_PALLETE[Int(bgPalette[2])]
case 3:
NESColor.SYSTEM_PALLETE[Int(bgPalette[3])]
default:
fatalError("Invalid Pallete Color type")
}
frame.setPixel((tileLoc.col * 8 + x, tileLoc.row * 8 + y), rgb)
}
}
// MARK: Draw Sprites
for i in stride(from: 0, to: ppu.oamData.count, by: 4) {
let tileIndex = UInt16(ppu.oamData[i + 1])
let tileX = Int(ppu.oamData[i + 3])
let tileY = Int(ppu.oamData[i])
let flipVert = ppu.oamData[i + 2] >> 7 & 1 == 1
let flipHori = ppu.oamData[i + 2] >> 6 & 1 == 1
let paletteIndex = ppu.oamData[i + 2] & 0b11
let spritePallete = getSpritePalette(ppu, paletteIndex: paletteIndex)
let bank = ppu.ctrl.spritePatternAddr()
let tile = ppu.chrRom[(bank + Int(tileIndex) * 16)...(bank + Int(tileIndex) * 16 + 15)]
for y in 0...7 { for y in 0...7 {
var upper = tile[tile.startIndex + y] var upper = tile[tile.startIndex + y]
var lower = tile[tile.startIndex + y + 8] var lower = tile[tile.startIndex + y + 8]
for x in (0...7).reversed() { for x in (0...7).reversed() {
let value = (1 & upper) << 1 | (1 & lower) let value = (1 & lower) << 1 | (1 & upper)
upper = upper >> 1 upper = upper >> 1
lower = lower >> 1 lower = lower >> 1
if (value == 0) {
continue // skip coloring this pixel, it's transparent
}
let rgb = switch value { let rgb = switch value {
case 0:
NESColor.SYSTEM_PALLETE[0x01]
case 1: case 1:
NESColor.SYSTEM_PALLETE[0x23] NESColor.SYSTEM_PALLETE[Int(spritePallete[1])]
case 2: case 2:
NESColor.SYSTEM_PALLETE[0x28] NESColor.SYSTEM_PALLETE[Int(spritePallete[2])]
case 3: case 3:
NESColor.SYSTEM_PALLETE[0x31] NESColor.SYSTEM_PALLETE[Int(spritePallete[3])]
default: default:
fatalError("Invalid Pallete Color type") fatalError("Invalid Pallete Color type")
} }
frame.setPixel((tileX * 8 + x, tileY * 8 + y), rgb) switch (flipHori, flipVert) {
case (false, false):
frame.setPixel((tileX + x, tileY + y), rgb)
case (true, false):
frame.setPixel((tileX + 7 - x, tileY + y), rgb)
case (false, true):
frame.setPixel((tileX + x, tileY + 7 - y), rgb)
case (true, true):
frame.setPixel((tileX + 7 - x, tileY + 7 - y), rgb)
} }
} }
} }
} }
}
}
static func getBgPalette(_ ppu: NesPPU, tileLoc: (col: Int, row: Int)) -> [UInt8] {
let attrTableIndex = tileLoc.row / 4 * 8 + tileLoc.col / 4
let attrByte = ppu.vram[0x3c0 + attrTableIndex] // FIXME: still using hardcoded first nametable
let palleteIndex = switch (tileLoc.col % 4 / 2, tileLoc.row % 4 / 2) {
case (0,0):
attrByte & 0b11
case (1,0):
(attrByte >> 2) & 0b11
case (0,1):
(attrByte >> 4) & 0b11
case (1,1):
(attrByte >> 6) & 0b11
default:
fatalError("Invalid titleLoc. This should never happen!")
}
let palleteStartIndex = 1 + Int(palleteIndex) * 4
return [ppu.paletteTable[0], ppu.paletteTable[palleteStartIndex], ppu.paletteTable[palleteStartIndex + 1], ppu.paletteTable[palleteStartIndex + 2]]
}
static func getSpritePalette(_ ppu: NesPPU, paletteIndex: UInt8) -> [UInt8] {
let start = 0x11 + Int(paletteIndex * 4)
return [
0,
ppu.paletteTable[start],
ppu.paletteTable[start + 1],
ppu.paletteTable[start + 2]
]
}
} }