diff --git a/Sources/Data/Bus.swift b/Sources/Data/Bus.swift index 68a7205..1d427f9 100644 --- a/Sources/Data/Bus.swift +++ b/Sources/Data/Bus.swift @@ -23,8 +23,11 @@ class Bus { func tick(_ cycles: UInt8) { self.cycles += Int(cycles) - let newFrame = self.ppu.tick(cycles * 3) - if newFrame { + + let nmiBefore = ppu.nmiInterrupt != nil + _ = self.ppu.tick(cycles * 3) + let nmiAfter = ppu.nmiInterrupt != nil + if !nmiBefore && nmiAfter { gameloopCallback(ppu) } diff --git a/Sources/PPU/NesPPU.swift b/Sources/PPU/NesPPU.swift index f160e15..cbd1ec0 100644 --- a/Sources/PPU/NesPPU.swift +++ b/Sources/PPU/NesPPU.swift @@ -28,6 +28,9 @@ class NesPPU { 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 @@ -50,6 +53,12 @@ class NesPPU { 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 diff --git a/Sources/PPU/Registers/ControlRegister.swift b/Sources/PPU/Registers/ControlRegister.swift index f4430fc..84b8da6 100644 --- a/Sources/PPU/Registers/ControlRegister.swift +++ b/Sources/PPU/Registers/ControlRegister.swift @@ -70,4 +70,19 @@ struct ControlRegister: OptionSet { 1 } } + + func nametableAddr() -> UInt16 { + switch rawValue & 0b11 { + case 0: + 0x2000 + case 1: + 0x2400 + case 2: + 0x2800 + case 3: + 0x2c00 + default: + fatalError("naemtableAddr: Not possible!") + } + } } diff --git a/Sources/Render/Render.swift b/Sources/Render/Render.swift index fc354bb..eb62f08 100644 --- a/Sources/Render/Render.swift +++ b/Sources/Render/Render.swift @@ -1,37 +1,40 @@ class Render { static func render(_ ppu: NesPPU, frame: Frame) { - let bank = ppu.ctrl.backgroundPatternAddr() + let scroll = (x: Int(ppu.scroll.x), y: Int(ppu.scroll.y)) - for i in 0..<0x03c0 { // FIXME: For now, just use first nametable - let tileAddr = UInt16(ppu.vram[i]) - let tileLoc = (col: i % 32, row: i / 32) - let tile = ppu.chrRom[(bank + Int(tileAddr) * 16)...(bank + Int(tileAddr) * 16 + 15)] - let bgPalette = getBgPalette(ppu, tileLoc: tileLoc) + let (mainNametable, secondNametable) = switch (ppu.mirroring, ppu.ctrl.nametableAddr()) { + case (.vertical, 0x2000), (.vertical, 0x2800), (.horizontal, 0x2000), (.horizontal, 0x2400): + (ppu.vram[0..<0x400], ppu.vram[0x400..<0x800]) + case (.vertical, 0x2400), (.vertical, 0x2c00), (.horizontal, 0x2800), (.horizontal, 0x2c00): + (ppu.vram[0x400..<0x800], ppu.vram[0..<0x400]) + default: + fatalError("cringe looking nametable arrangment: \(ppu.mirroring)") + } - // MARK: Draw Background - for y in 0...7 { - var upper = tile[tile.startIndex + y] - var lower = tile[tile.startIndex + y + 8] + renderNameTable( + ppu, + frame: frame, + nameTable: Array(mainNametable), + viewPort: Rect(x1: scroll.x, y1: scroll.y, x2: 256, y2: 240), + shift: (-scroll.x, -scroll.y) + ) - for x in [7,6,5,4,3,2,1,0] { - 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) - } - } + if scroll.x > 0 { + renderNameTable( + ppu, + frame: frame, + nameTable: Array(secondNametable), + viewPort: Rect(x1: 0, y1: 0, x2: scroll.x, y2: 240), + shift: (256 - scroll.x, 0) + ) + } else if scroll.y > 0 { + renderNameTable( + ppu, + frame: frame, + nameTable: Array(secondNametable), + viewPort: Rect(x1: 0, y1: 0, x2: 256, y2: scroll.y), + shift: (0, 240 - scroll.y) + ) } // MARK: Draw Sprites @@ -85,9 +88,50 @@ class Render { } } - static func getBgPalette(_ ppu: NesPPU, tileLoc: (col: Int, row: Int)) -> [UInt8] { + static func renderNameTable(_ ppu: NesPPU, frame: Frame, nameTable: [UInt8], viewPort: Rect, shift: (x: Int, y: Int)) { + let bank = ppu.ctrl.backgroundPatternAddr() + let attributeTable = nameTable[0x3c0..<0x400] + + for i in 0..<0x3c0 { + let tileAddr = UInt16(nameTable[i]) + let tileLoc = (col: i % 32, row: i / 32) + let tile = ppu.chrRom[(bank + Int(tileAddr) * 16)...(bank + Int(tileAddr) * 16 + 15)] + let bgPalette = getBgPalette(ppu, tileLoc: tileLoc, attributeTable: Array(attributeTable)) + + // MARK: Draw Background + for y in 0...7 { + var upper = tile[tile.startIndex + y] + var lower = tile[tile.startIndex + y + 8] + + for x in [7,6,5,4,3,2,1,0] { + 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") + } + + let pixelLoc = (x: tileLoc.col * 8 + x, y: tileLoc.row * 8 + y) + if pixelLoc.x >= viewPort.x1 && pixelLoc.x < viewPort.x2 && pixelLoc.y >= viewPort.y1 && pixelLoc.y < viewPort.y2 { + frame.setPixel((shift.x + pixelLoc.x, shift.y + pixelLoc.y), rgb) + } + } + } + } + } + + static func getBgPalette(_ ppu: NesPPU, tileLoc: (col: Int, row: Int), attributeTable: [UInt8]) -> [UInt8] { let attrTableIndex = tileLoc.row / 4 * 8 + tileLoc.col / 4 - let attrByte = ppu.vram[0x3c0 + attrTableIndex] // FIXME: still using hardcoded first nametable + let attrByte = attributeTable[attrTableIndex] let palleteIndex = switch (tileLoc.col % 4 / 2, tileLoc.row % 4 / 2) { case (0,0): @@ -103,7 +147,12 @@ class Render { } let palleteStartIndex = 1 + Int(palleteIndex) * 4 - return [ppu.paletteTable[0], ppu.paletteTable[palleteStartIndex], ppu.paletteTable[palleteStartIndex + 1], ppu.paletteTable[palleteStartIndex + 2]] + return [ + ppu.paletteTable[0], + ppu.paletteTable[palleteStartIndex], + ppu.paletteTable[palleteStartIndex + 1], + ppu.paletteTable[palleteStartIndex + 2], + ] } static func getSpritePalette(_ ppu: NesPPU, paletteIndex: UInt8) -> [UInt8] { @@ -116,3 +165,10 @@ class Render { ] } } + +struct Rect { + let x1: Int + let y1: Int + let x2: Int + let y2: Int +} diff --git a/Sources/main.swift b/Sources/main.swift index a81676a..cc50462 100644 --- a/Sources/main.swift +++ b/Sources/main.swift @@ -22,7 +22,8 @@ var texture = SDL_CreateTexture(canvas, SDL_PIXELFORMAT_RGB24.rawValue, Int32(SD var event = SDL_Event() -guard let bytes = NSData(contentsOfFile: "pacman.nes") else { fatalError("Rom not found") } +guard let bytes = NSData(contentsOfFile: "smb1.nes") else { fatalError("Rom not found") } + var gameCode = [UInt8](repeating: 0, count: bytes.length) bytes.getBytes(&gameCode, length: bytes.length)