From bd12a6133004f81d6c5eed83f4ac7c9d0dd486db Mon Sep 17 00:00:00 2001 From: Andrew Glaze Date: Tue, 20 Aug 2024 12:07:36 -0400 Subject: [PATCH] Render: implement basic chr rom renderer --- Sources/Render/Frame.swift | 34 ++++++ Sources/Render/TileViewer.swift | 80 +++++++++++++ Sources/main.swift | 199 ++++++++++++++++++-------------- 3 files changed, 228 insertions(+), 85 deletions(-) create mode 100644 Sources/Render/Frame.swift create mode 100644 Sources/Render/TileViewer.swift diff --git a/Sources/Render/Frame.swift b/Sources/Render/Frame.swift new file mode 100644 index 0000000..aadb9ce --- /dev/null +++ b/Sources/Render/Frame.swift @@ -0,0 +1,34 @@ +class Frame { + var data: [UInt8] = [UInt8](repeating: 0, count: WIDTH * HEIGHT * 3) + + static let WIDTH = 256 + static let HEIGHT = 240 + + func setPixel(_ loc: (x: Int, y: Int), _ color: (r: UInt8, g: UInt8, b: UInt8)) { + let base = loc.y * 3 * Frame.WIDTH + loc.x * 3 + if base + 2 < data.count { + data[base] = color.r + data[base + 1] = color.g + data[base + 2] = color.b + } + } + +} + +struct NESColor { + static let SYSTEM_PALLETE: [(UInt8, UInt8, UInt8)] = [ + (0x80, 0x80, 0x80), (0x00, 0x3D, 0xA6), (0x00, 0x12, 0xB0), (0x44, 0x00, 0x96), (0xA1, 0x00, 0x5E), + (0xC7, 0x00, 0x28), (0xBA, 0x06, 0x00), (0x8C, 0x17, 0x00), (0x5C, 0x2F, 0x00), (0x10, 0x45, 0x00), + (0x05, 0x4A, 0x00), (0x00, 0x47, 0x2E), (0x00, 0x41, 0x66), (0x00, 0x00, 0x00), (0x05, 0x05, 0x05), + (0x05, 0x05, 0x05), (0xC7, 0xC7, 0xC7), (0x00, 0x77, 0xFF), (0x21, 0x55, 0xFF), (0x82, 0x37, 0xFA), + (0xEB, 0x2F, 0xB5), (0xFF, 0x29, 0x50), (0xFF, 0x22, 0x00), (0xD6, 0x32, 0x00), (0xC4, 0x62, 0x00), + (0x35, 0x80, 0x00), (0x05, 0x8F, 0x00), (0x00, 0x8A, 0x55), (0x00, 0x99, 0xCC), (0x21, 0x21, 0x21), + (0x09, 0x09, 0x09), (0x09, 0x09, 0x09), (0xFF, 0xFF, 0xFF), (0x0F, 0xD7, 0xFF), (0x69, 0xA2, 0xFF), + (0xD4, 0x80, 0xFF), (0xFF, 0x45, 0xF3), (0xFF, 0x61, 0x8B), (0xFF, 0x88, 0x33), (0xFF, 0x9C, 0x12), + (0xFA, 0xBC, 0x20), (0x9F, 0xE3, 0x0E), (0x2B, 0xF0, 0x35), (0x0C, 0xF0, 0xA4), (0x05, 0xFB, 0xFF), + (0x5E, 0x5E, 0x5E), (0x0D, 0x0D, 0x0D), (0x0D, 0x0D, 0x0D), (0xFF, 0xFF, 0xFF), (0xA6, 0xFC, 0xFF), + (0xB3, 0xEC, 0xFF), (0xDA, 0xAB, 0xEB), (0xFF, 0xA8, 0xF9), (0xFF, 0xAB, 0xB3), (0xFF, 0xD2, 0xB0), + (0xFF, 0xEF, 0xA6), (0xFF, 0xF7, 0x9C), (0xD7, 0xE8, 0x95), (0xA6, 0xED, 0xAF), (0xA2, 0xF2, 0xDA), + (0x99, 0xFF, 0xFC), (0xDD, 0xDD, 0xDD), (0x11, 0x11, 0x11), (0x11, 0x11, 0x11) + ] +} diff --git a/Sources/Render/TileViewer.swift b/Sources/Render/TileViewer.swift new file mode 100644 index 0000000..2d54d22 --- /dev/null +++ b/Sources/Render/TileViewer.swift @@ -0,0 +1,80 @@ +struct TileViewer { + static func showTile(chrRom: [UInt8], bank: Int, tileNum: Int) -> Frame { + guard bank <= 1 else { fatalError("CHR Rom bank must be >1") } + + let frame = Frame() + let bank = bank * 0x1000 + + let tile = chrRom[(bank + tileNum * 16)...(bank + tileNum * 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((x, y), rgb) + } + } + + return frame + } + + static func showTileBank(chrRom: [UInt8], bank: Int) -> Frame { + let frame = Frame() + var tileY = 0 + var tileX = 0 + let bank = (bank * 0x1000) + + for tileNum in 0..<255 { + if tileNum != 0 && tileNum % 20 == 0 { + tileY += 10; + tileX = 0; + } + + let tile = chrRom[(bank + tileNum * 16)...(bank + tileNum * 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 + x, tileY + y), rgb) + } + } + + tileX += 10 + } + + return frame + } +} diff --git a/Sources/main.swift b/Sources/main.swift index d641d3a..bc2ba96 100644 --- a/Sources/main.swift +++ b/Sources/main.swift @@ -9,41 +9,117 @@ guard SDL_Init(SDL_INIT_VIDEO) == 0 else { } let window = SDL_CreateWindow( - "Snake Game", + "SwiftNES", Int32(SDL_WINDOWPOS_CENTERED_MASK), Int32(SDL_WINDOWPOS_CENTERED_MASK), - Int32(32.0 * 10.0), Int32(32.0 * 10.0), + Int32(256.0 * 3.0), Int32(240.0 * 3.0), SDL_WINDOW_SHOWN.rawValue) let canvas = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC.rawValue) -SDL_RenderSetScale(canvas, 10.0, 10.0) +SDL_RenderSetScale(canvas, 3.0, 3.0) -var texture = SDL_CreateTexture(canvas, SDL_PIXELFORMAT_RGB24.rawValue, Int32(SDL_TEXTUREACCESS_TARGET.rawValue), 32, 32) +var texture = SDL_CreateTexture(canvas, SDL_PIXELFORMAT_RGB24.rawValue, Int32(SDL_TEXTUREACCESS_TARGET.rawValue), 256, 240) var event = SDL_Event() var quit = false -func handleUserInput(_ cpu: CPU, event: inout SDL_Event) { +// func handleUserInput(_ cpu: CPU, event: inout SDL_Event) { +// 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) +// case SDLK_w: +// cpu.memWrite(0xff, data: 0x77) +// case SDLK_a: +// cpu.memWrite(0xff, data: 0x61) +// case SDLK_s: +// cpu.memWrite(0xff, data: 0x73) +// case SDLK_d: +// cpu.memWrite(0xff, data: 0x64) +// default: +// continue +// } +// } +// } +// } + +// func color(_ byte: UInt8) -> SDL_Color { +// switch byte{ +// case 0: +// return SDL_Color(r: 0, g: 0, b: 0, a: 255) +// case 1: +// return SDL_Color(r: 255, g: 255, b: 255, a: 255) +// case 2, 9: +// return SDL_Color(r: 128, g: 128, b: 128, a: 255) +// case 3, 10: +// return SDL_Color(r: 255, g: 0, b: 0, a: 255) +// case 4, 11: +// return SDL_Color(r: 0, g: 255, b: 0, a: 255) +// case 5, 12: +// return SDL_Color(r: 0, g: 0, b: 255, a: 255) +// case 6, 13: +// return SDL_Color(r: 255, g: 0, b: 255, a: 255) +// case 7, 14: +// return SDL_Color(r: 255, g: 255, b: 0, a: 255) +// default: +// return SDL_Color(r: 0, g: 255, b: 255, a: 255) + +// } +// } + +// func readScreenState(_ cpu: CPU, frame: inout [UInt8]) -> Bool { + +// var frame_idx = 0 +// var update = false +// for i in 0x0200..<0x600 { +// let color_idx = cpu.memRead(UInt16(i)) +// let color = color(color_idx) +// let (b1, b2, b3) = (color.r, color.b, color.g) +// if frame[frame_idx] != b1 || frame[frame_idx + 1] != b2 || frame[frame_idx + 2] != b3 { +// frame[frame_idx] = b1; +// frame[frame_idx + 1] = b2; +// frame[frame_idx + 2] = b3; +// update = true; +// } +// frame_idx += 3 +// } +// return update +// } + +guard let bytes = NSData(contentsOfFile: "pacman.nes") else { fatalError("Rom not found") } +var gameCode = [UInt8](repeating: 0, count: bytes.length) +bytes.getBytes(&gameCode, length: bytes.length) + +let rom = try Rom(gameCode) + +//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) - } + 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) - case SDLK_w: - cpu.memWrite(0xff, data: 0x77) - case SDLK_a: - cpu.memWrite(0xff, data: 0x61) - case SDLK_s: - cpu.memWrite(0xff, data: 0x73) - case SDLK_d: - cpu.memWrite(0xff, data: 0x64) default: continue } @@ -51,79 +127,32 @@ func handleUserInput(_ cpu: CPU, event: inout SDL_Event) { } } -func color(_ byte: UInt8) -> SDL_Color { - switch byte{ - case 0: - return SDL_Color(r: 0, g: 0, b: 0, a: 255) - case 1: - return SDL_Color(r: 255, g: 255, b: 255, a: 255) - case 2, 9: - return SDL_Color(r: 128, g: 128, b: 128, a: 255) - case 3, 10: - return SDL_Color(r: 255, g: 0, b: 0, a: 255) - case 4, 11: - return SDL_Color(r: 0, g: 255, b: 0, a: 255) - case 5, 12: - return SDL_Color(r: 0, g: 0, b: 255, a: 255) - case 6, 13: - return SDL_Color(r: 255, g: 0, b: 255, a: 255) - case 7, 14: - return SDL_Color(r: 255, g: 255, b: 0, a: 255) - default: - return SDL_Color(r: 0, g: 255, b: 255, a: 255) +// let bus = Bus(try! Rom(gameCode)) - } -} - -func readScreenState(_ cpu: CPU, frame: inout [UInt8]) -> Bool { - - var frame_idx = 0 - var update = false - for i in 0x0200..<0x600 { - let color_idx = cpu.memRead(UInt16(i)) - let color = color(color_idx) - let (b1, b2, b3) = (color.r, color.b, color.g) - if frame[frame_idx] != b1 || frame[frame_idx + 1] != b2 || frame[frame_idx + 2] != b3 { - frame[frame_idx] = b1; - frame[frame_idx + 1] = b2; - frame[frame_idx + 2] = b3; - update = true; - } - frame_idx += 3 - } - return update -} - -guard let rom = NSData(contentsOfFile: "snake.nes") else { fatalError("Rom not found") } -var gameCode = [UInt8](repeating: 0, count: rom.length) -rom.getBytes(&gameCode, length: rom.length) - -let bus = Bus(try! Rom(gameCode)) - -var cpu = CPU(bus: bus) +//var cpu = CPU(bus: bus) //cpu.load(gameCode) -cpu.reset() +//cpu.reset() //cpu.programCounter = 0xC000 -var screenState = [UInt8](repeating: 0, count: 32 * 3 * 32) -var rng = SystemRandomNumberGenerator() -cpu.run(onCycle: { - //print(dumpCpuState(cpu)) - handleUserInput(cpu, event: &event) - cpu.memWrite(0xfe, data: UInt8.random(in: 1...16, using: &rng)) +// var screenState = [UInt8](repeating: 0, count: 32 * 3 * 32) +// var rng = SystemRandomNumberGenerator() +// cpu.run(onCycle: { +// //print(dumpCpuState(cpu)) +// handleUserInput(cpu, event: &event) +// cpu.memWrite(0xfe, data: UInt8.random(in: 1...16, using: &rng)) - if readScreenState(cpu, frame: &screenState) { - SDL_UpdateTexture(texture, nil, screenState, 32 * 3) - SDL_RenderCopy(canvas, texture, nil, nil) - SDL_RenderPresent(canvas) - } +// if readScreenState(cpu, frame: &screenState) { +// SDL_UpdateTexture(texture, nil, screenState, 32 * 3) +// SDL_RenderCopy(canvas, texture, nil, nil) +// SDL_RenderPresent(canvas) +// } - usleep(70) -}, onComplete: { - SDL_DestroyWindow(window) - SDL_Quit() - exit(0) -}) +// usleep(70) +// }, onComplete: { +// SDL_DestroyWindow(window) +// SDL_Quit() +// exit(0) +// }) // Infinite loop otherwise the program will exit prematurely -RunLoop.main.run() +// RunLoop.main.run()