Render: implement basic chr rom renderer

This commit is contained in:
Andrew Glaze 2024-08-20 12:07:36 -04:00
parent 830bc5f65a
commit bd12a61330
3 changed files with 228 additions and 85 deletions

View File

@ -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)
]
}

View File

@ -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
}
}

View File

@ -9,21 +9,105 @@ guard SDL_Init(SDL_INIT_VIDEO) == 0 else {
} }
let window = SDL_CreateWindow( let window = SDL_CreateWindow(
"Snake Game", "SwiftNES",
Int32(SDL_WINDOWPOS_CENTERED_MASK), Int32(SDL_WINDOWPOS_CENTERED_MASK), 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) SDL_WINDOW_SHOWN.rawValue)
let canvas = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC.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 event = SDL_Event()
var quit = false 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 { while SDL_PollEvent(&event) > 0 {
if event.type == SDL_QUIT.rawValue { if event.type == SDL_QUIT.rawValue {
SDL_DestroyWindow(window) SDL_DestroyWindow(window)
@ -36,14 +120,6 @@ func handleUserInput(_ cpu: CPU, event: inout SDL_Event) {
SDL_DestroyWindow(window) SDL_DestroyWindow(window)
SDL_Quit() SDL_Quit()
exit(0) 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: default:
continue continue
} }
@ -51,79 +127,32 @@ func handleUserInput(_ cpu: CPU, event: inout SDL_Event) {
} }
} }
func color(_ byte: UInt8) -> SDL_Color { // let bus = Bus(try! Rom(gameCode))
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)
} //var cpu = CPU(bus: bus)
}
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)
//cpu.load(gameCode) //cpu.load(gameCode)
cpu.reset() //cpu.reset()
//cpu.programCounter = 0xC000 //cpu.programCounter = 0xC000
var screenState = [UInt8](repeating: 0, count: 32 * 3 * 32) // var screenState = [UInt8](repeating: 0, count: 32 * 3 * 32)
var rng = SystemRandomNumberGenerator() // var rng = SystemRandomNumberGenerator()
cpu.run(onCycle: { // cpu.run(onCycle: {
//print(dumpCpuState(cpu)) // //print(dumpCpuState(cpu))
handleUserInput(cpu, event: &event) // handleUserInput(cpu, event: &event)
cpu.memWrite(0xfe, data: UInt8.random(in: 1...16, using: &rng)) // cpu.memWrite(0xfe, data: UInt8.random(in: 1...16, using: &rng))
if readScreenState(cpu, frame: &screenState) { // if readScreenState(cpu, frame: &screenState) {
SDL_UpdateTexture(texture, nil, screenState, 32 * 3) // SDL_UpdateTexture(texture, nil, screenState, 32 * 3)
SDL_RenderCopy(canvas, texture, nil, nil) // SDL_RenderCopy(canvas, texture, nil, nil)
SDL_RenderPresent(canvas) // SDL_RenderPresent(canvas)
} // }
usleep(70) // usleep(70)
}, onComplete: { // }, onComplete: {
SDL_DestroyWindow(window) // SDL_DestroyWindow(window)
SDL_Quit() // SDL_Quit()
exit(0) // exit(0)
}) // })
// Infinite loop otherwise the program will exit prematurely // Infinite loop otherwise the program will exit prematurely
RunLoop.main.run() // RunLoop.main.run()