palette and pattern tables
This commit is contained in:
74
src/main.rs
74
src/main.rs
@@ -61,8 +61,8 @@ fn main() -> Result<(), Error> {
|
||||
// Prepare window and drawing resources
|
||||
|
||||
// debugger + scaled nes resolution + border
|
||||
let window_width = 300 + 256 * 2 + 5;
|
||||
let window_height = 40 + 240*2 + 5;
|
||||
let window_width = 300 + 256 * 3 + 5;
|
||||
let window_height = 40 + 240 * 3 + 5;
|
||||
let mut window: PistonWindow = WindowSettings::new("xXx NESemu xXx", [window_width, window_height])
|
||||
.exit_on_esc(true).graphics_api(OpenGL::V3_2).build().unwrap();
|
||||
let mut event_settings = EventSettings::new();
|
||||
@@ -78,33 +78,87 @@ fn main() -> Result<(), Error> {
|
||||
// fps counter
|
||||
let mut fps = FPSCounter::new();
|
||||
|
||||
// main screen texture
|
||||
// screen textures
|
||||
let mut texture_ctx = TextureContext {
|
||||
factory: window.factory.clone(),
|
||||
encoder: window.factory.create_command_buffer().into(),
|
||||
};
|
||||
let mut texture: G2dTexture = Texture::from_image(
|
||||
|
||||
// main screen
|
||||
let mut main_texture: G2dTexture = Texture::from_image(
|
||||
&mut texture_ctx,
|
||||
&nes.ppu.borrow().canvas_main,
|
||||
&TextureSettings::new()
|
||||
).unwrap();
|
||||
|
||||
// pattern table textures
|
||||
let mut pattern_table_textures: Vec<G2dTexture> = (0..2).map({|i|
|
||||
Texture::from_image(
|
||||
&mut texture_ctx,
|
||||
&nes.ppu.borrow().pattern_tables[i],
|
||||
&TextureSettings::new()
|
||||
).unwrap()
|
||||
}).collect();
|
||||
|
||||
// palette textures
|
||||
let mut palette_textures: Vec<G2dTexture> = (0..8).map({|i|
|
||||
Texture::from_image(
|
||||
&mut texture_ctx,
|
||||
&nes.ppu.borrow().palettes[i],
|
||||
&TextureSettings::new()
|
||||
).unwrap()
|
||||
}).collect();
|
||||
|
||||
// Main loop
|
||||
let mut run = false;
|
||||
let mut run = true;
|
||||
while let Some(event) = events.next(&mut window) {
|
||||
if let Some(_) = event.render_args() {
|
||||
// Run enough clocks to render the next frame
|
||||
// if run { nes.clock_frame(); }
|
||||
if run { nes.clock_scanline(); }
|
||||
if run { nes.clock_frame(); }
|
||||
|
||||
|
||||
texture.update(&mut texture_ctx, &nes.ppu.borrow().canvas_main).unwrap();
|
||||
{
|
||||
let mut ppu = nes.ppu.borrow_mut();
|
||||
let ppu_bus = nes.ppu_bus.borrow();
|
||||
// main_texture.update(&mut texture_ctx, &ppu.canvas_main).unwrap();
|
||||
pattern_table_textures[0].update(&mut texture_ctx, &ppu.get_pattern_table(&*ppu_bus, 0, 1)).unwrap();
|
||||
pattern_table_textures[1].update(&mut texture_ctx, &ppu.get_pattern_table(&*ppu_bus, 1, 1)).unwrap();
|
||||
palette_textures[0].update(&mut texture_ctx, &ppu.get_palette(&*ppu_bus, 0)).unwrap();
|
||||
palette_textures[1].update(&mut texture_ctx, &ppu.get_palette(&*ppu_bus, 1)).unwrap();
|
||||
palette_textures[2].update(&mut texture_ctx, &ppu.get_palette(&*ppu_bus, 2)).unwrap();
|
||||
palette_textures[3].update(&mut texture_ctx, &ppu.get_palette(&*ppu_bus, 3)).unwrap();
|
||||
palette_textures[4].update(&mut texture_ctx, &ppu.get_palette(&*ppu_bus, 4)).unwrap();
|
||||
palette_textures[5].update(&mut texture_ctx, &ppu.get_palette(&*ppu_bus, 5)).unwrap();
|
||||
palette_textures[6].update(&mut texture_ctx, &ppu.get_palette(&*ppu_bus, 6)).unwrap();
|
||||
palette_textures[7].update(&mut texture_ctx, &ppu.get_palette(&*ppu_bus, 7)).unwrap();
|
||||
}
|
||||
window.draw_2d(&event, |c, g, d| {
|
||||
clear(BG_COLOR, g);
|
||||
texture_ctx.encoder.flush(d);
|
||||
let transform = c.transform.trans(300.0, 40.0).scale(2.0, 2.0);
|
||||
image(&texture, transform, g);
|
||||
let transform = c.transform.trans(300.0, 40.0).scale(3.0, 3.0);
|
||||
image(&main_texture, transform, g);
|
||||
|
||||
let mut transform = c.transform.trans(10.0, 480.0).scale(7.0, 7.0);
|
||||
image(&palette_textures[0], transform, g);
|
||||
transform = transform.trans(5.0, 0.0);
|
||||
image(&palette_textures[1], transform, g);
|
||||
transform = transform.trans(5.0, 0.0);
|
||||
image(&palette_textures[2], transform, g);
|
||||
transform = transform.trans(5.0, 0.0);
|
||||
image(&palette_textures[3], transform, g);
|
||||
transform = transform.trans(5.0, 0.0);
|
||||
image(&palette_textures[4], transform, g);
|
||||
transform = transform.trans(5.0, 0.0);
|
||||
image(&palette_textures[5], transform, g);
|
||||
transform = transform.trans(5.0, 0.0);
|
||||
image(&palette_textures[6], transform, g);
|
||||
transform = transform.trans(5.0, 0.0);
|
||||
image(&palette_textures[7], transform, g);
|
||||
|
||||
let mut transform = c.transform.trans(10.0, 490.0).scale(1.0, 1.0);
|
||||
image(&pattern_table_textures[0], transform, g);
|
||||
transform = transform.trans(148.0, 0.0).scale(1.0, 1.0);
|
||||
image(&pattern_table_textures[1], transform, g);
|
||||
|
||||
// fps
|
||||
let fps = fps.tick();
|
||||
|
||||
@@ -25,7 +25,7 @@ pub struct NES {
|
||||
pub cpu: CPU,
|
||||
pub bus: Bus,
|
||||
pub ppu: Rc<RefCell<PPU>>,
|
||||
pub ppu_bus: PPUBus,
|
||||
pub ppu_bus: Rc<RefCell<PPUBus>>,
|
||||
pub clock_count: u64,
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ impl NES {
|
||||
cpu: CPU::new(),
|
||||
bus: Bus::new(ppu.clone(), ppu_bus.clone()),
|
||||
ppu: ppu.clone(),
|
||||
ppu_bus: PPUBus::new(),
|
||||
ppu_bus: ppu_bus.clone(),
|
||||
clock_count: 0,
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ impl NES {
|
||||
// both buses need to be connected to the cartridge
|
||||
let cart = Rc::new(RefCell::new(cartridge));
|
||||
self.bus.insert_cartridge(cart.clone());
|
||||
self.ppu_bus.insert_cartridge(cart.clone())
|
||||
self.ppu_bus.borrow_mut().insert_cartridge(cart.clone())
|
||||
}
|
||||
|
||||
// Initializes the NES CPU programm pointer
|
||||
@@ -74,7 +74,7 @@ impl NES {
|
||||
if self.clock_count % 3 == 0 {
|
||||
self.cpu.clock(&mut self.bus);
|
||||
}
|
||||
self.ppu.borrow_mut().clock(&mut self.ppu_bus);
|
||||
self.ppu.borrow_mut().clock(&mut *self.ppu_bus.borrow_mut());
|
||||
if self.clock_count % 100000 == 0 {
|
||||
info!("clock {}", self.clock_count);
|
||||
}
|
||||
|
||||
@@ -99,7 +99,8 @@ pub struct PPU {
|
||||
pub cycle: u16,
|
||||
pub scanline: u16,
|
||||
pub canvas_main: Sprite,
|
||||
pattern_table: [Sprite; 2],
|
||||
pub pattern_tables: [Sprite; 2],
|
||||
pub palettes: [Sprite; 8],
|
||||
pub frame_ready: bool,
|
||||
addr_latch_set: bool,
|
||||
data_buffer: Byte,
|
||||
@@ -112,7 +113,20 @@ impl PPU {
|
||||
cycle: 0,
|
||||
scanline: 0,
|
||||
canvas_main: ImageBuffer::from_pixel(256, 240, PALETTE[&0x00]),
|
||||
pattern_table: [ImageBuffer::new(128, 128), ImageBuffer::new(128, 128)],
|
||||
pattern_tables: [
|
||||
ImageBuffer::from_pixel(128, 128, PALETTE[&0x00]),
|
||||
ImageBuffer::from_pixel(128, 128, PALETTE[&0x00])
|
||||
],
|
||||
palettes: [
|
||||
ImageBuffer::from_pixel(4, 1, PALETTE[&0x00]),
|
||||
ImageBuffer::from_pixel(4, 1, PALETTE[&0x00]),
|
||||
ImageBuffer::from_pixel(4, 1, PALETTE[&0x00]),
|
||||
ImageBuffer::from_pixel(4, 1, PALETTE[&0x00]),
|
||||
ImageBuffer::from_pixel(4, 1, PALETTE[&0x00]),
|
||||
ImageBuffer::from_pixel(4, 1, PALETTE[&0x00]),
|
||||
ImageBuffer::from_pixel(4, 1, PALETTE[&0x00]),
|
||||
ImageBuffer::from_pixel(4, 1, PALETTE[&0x00]),
|
||||
],
|
||||
frame_ready: false,
|
||||
addr_latch_set: false,
|
||||
data_buffer: 0
|
||||
@@ -265,6 +279,30 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
|
||||
// get a colored pixel using the NES color palette for given palette_id
|
||||
// and pixel value
|
||||
fn get_color_from_ram<T: PPUMemory>(&self, mem: &T, palette_id: u8, pixel: u8) -> Pixel {
|
||||
// 0x3F00: Start of palette memory
|
||||
// palette << 2: Palette size is 4
|
||||
// pixel: pixel index is 0,1,2 or 3
|
||||
// 0x3F (63): limits reading to PALETTE size
|
||||
let palette_idx_addr = 0x3F00 + ((palette_id as Word) << 2) + pixel as Word;
|
||||
// println!(palette_id_addr);
|
||||
let palette_idx = mem.readb_ppu(palette_idx_addr) & 0x3F;
|
||||
PALETTE[&palette_idx]
|
||||
}
|
||||
|
||||
// updates the palette from VRAM and returns a sprite with the 4 colors
|
||||
pub fn get_palette<T: PPUMemory>(&mut self, mem: &T, palette_id: u8) -> &Sprite {
|
||||
for i in 0..4 {
|
||||
self.palettes[palette_id as usize].put_pixel(
|
||||
i, 0,
|
||||
self.get_color_from_ram(mem, palette_id, i as u8)
|
||||
);
|
||||
}
|
||||
&self.palettes[palette_id as usize]
|
||||
}
|
||||
|
||||
// Get the correct color for the pixel from the given palette
|
||||
fn get_color(&self, pixel: Byte, _palette: Byte) -> Pixel {
|
||||
// TODO: not implemented yet. returns some black and white color
|
||||
@@ -278,7 +316,7 @@ impl PPU {
|
||||
|
||||
// Get one of the two pattern tables of the PPU
|
||||
// This also initializes/updates the pattern table
|
||||
fn get_pattern_table<T: PPUMemory>(&mut self, index: usize, _palette: Byte, mem: &T) -> &Sprite {
|
||||
pub fn get_pattern_table<T: PPUMemory>(&mut self, mem: &T, index: usize, palette_id: Byte) -> &Sprite {
|
||||
// 16 x 16 tiles of 8x8px sprites per pattern table => 128x128px
|
||||
for x in 0..16 { // tile row
|
||||
for y in 0..16 { // tile column
|
||||
@@ -298,10 +336,10 @@ impl PPU {
|
||||
let mut tile_msb = mem.readb_ppu(tile_addr + 8);
|
||||
for col in 0..8 {
|
||||
let pixel = (tile_lsb & 0x01) + (tile_msb & 0x01);
|
||||
self.pattern_table[index].put_pixel(
|
||||
self.pattern_tables[index].put_pixel(
|
||||
(x * 8 + (7-col)) as u32, // x starts on the right, sprit is from left
|
||||
(y * 8 + row) as u32,
|
||||
self.get_color(123, pixel));
|
||||
self.get_color_from_ram(mem, palette_id, pixel));
|
||||
tile_lsb >>= 1;
|
||||
tile_msb >>= 1;
|
||||
}
|
||||
@@ -309,14 +347,14 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
|
||||
&self.pattern_table[index]
|
||||
&self.pattern_tables[index]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::nes::memory::PPUBus;
|
||||
use crate::nes::ppubus::PPUBus;
|
||||
|
||||
#[test]
|
||||
fn test_set_flags() {
|
||||
|
||||
@@ -37,8 +37,13 @@ impl PPUMemory for PPUBus {
|
||||
fn readb_ppu(&self, addr: Addr) -> Byte {
|
||||
// Palette is never mapped to cartridge
|
||||
if PALETTE_ADDR_RANGE[0] <= addr && addr <= PALETTE_ADDR_RANGE[1] {
|
||||
let rel_addr = addr - 0x3F00;
|
||||
return self.palette_memory[(rel_addr % 0x0020) as usize]
|
||||
let mut rel_addr = addr - 0x3F00;
|
||||
rel_addr = rel_addr % 0x0020;
|
||||
if rel_addr == 0x0010 { rel_addr = 0x0000 }
|
||||
if rel_addr == 0x0014 { rel_addr = 0x0004 }
|
||||
if rel_addr == 0x0018 { rel_addr = 0x0008 }
|
||||
if rel_addr == 0x001C { rel_addr = 0x000C }
|
||||
return self.palette_memory[(rel_addr) as usize]
|
||||
}
|
||||
|
||||
// give the cartridge a chance to handle the rest
|
||||
@@ -71,7 +76,12 @@ impl PPUMemory for PPUBus {
|
||||
fn writeb_ppu(&mut self, addr: Addr, data: Byte) {
|
||||
// Palette is never mapped to cartridge
|
||||
if PALETTE_ADDR_RANGE[0] <= addr && addr <= PALETTE_ADDR_RANGE[1] {
|
||||
let rel_addr = addr - 0x3F00;
|
||||
let mut rel_addr = addr - 0x3F00;
|
||||
rel_addr = rel_addr % 0x0020;
|
||||
if rel_addr == 0x0010 { rel_addr = 0x0000 }
|
||||
if rel_addr == 0x0014 { rel_addr = 0x0004 }
|
||||
if rel_addr == 0x0018 { rel_addr = 0x0008 }
|
||||
if rel_addr == 0x001C { rel_addr = 0x000C }
|
||||
self.palette_memory[(rel_addr % 0x0020) as usize] = data;
|
||||
}
|
||||
|
||||
@@ -246,14 +256,29 @@ mod tests {
|
||||
|
||||
// read/write something to palette memory
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
assert_eq!(0, mem.readb_ppu(addr));
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
println!("Written {} to {:#08x}", idx, addr);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// assert palette are initialized correctly
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
// mirroring makes this look a bit strange, but this is the expected
|
||||
// result for writing numbers to 0x3F00 to 0x3F1F
|
||||
let expected = [
|
||||
16,
|
||||
1, 2, 3, 20,
|
||||
5, 6, 7, 24,
|
||||
9, 10, 11, 28,
|
||||
13, 14, 15, 16,
|
||||
17, 18, 19, 20,
|
||||
21, 22, 23, 24,
|
||||
25, 26, 27, 28,
|
||||
29, 30, 31,
|
||||
];
|
||||
|
||||
for (addr, expected) in (0x3F00 .. 0x3F1F + 1).zip(expected.iter()) {
|
||||
assert_eq!(expected, &mem.readb_ppu(addr),
|
||||
"{:#08x}", addr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +305,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_palette_internal_mirroring() {
|
||||
|
||||
let wired_mirrors = [
|
||||
(0x3F10, 0x3F00),
|
||||
(0x3F14, 0x3F04),
|
||||
(0x3F18, 0x3F08),
|
||||
(0x3F1C, 0x3F0C),
|
||||
];
|
||||
for (idx, (addr, true_addr)) in wired_mirrors.iter().enumerate() {
|
||||
let mut mem = PPUBus::new();
|
||||
mem.writeb_ppu(*true_addr as Addr, idx as Byte);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(*true_addr as Addr));
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(*addr as Addr));
|
||||
mem.writeb_ppu(*true_addr as Addr, idx as Byte + 1);
|
||||
assert_eq!(idx as Byte + 1, mem.readb_ppu(*true_addr as Addr));
|
||||
assert_eq!(idx as Byte + 1, mem.readb_ppu(*addr as Addr));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ppu_memory_palette_mirroring() {
|
||||
let mut mem = PPUBus::new();
|
||||
@@ -287,40 +332,56 @@ mod tests {
|
||||
// read/write something to palette memory
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte);
|
||||
println!("Written {} to {:#08x}", idx, addr);
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// assert palette are initialized correctly
|
||||
// mirroring makes this look a bit strange, but this is the expected
|
||||
// result for writing numbers to 0x3F00 to 0x3F1F
|
||||
let expected = [
|
||||
16,
|
||||
1, 2, 3, 20,
|
||||
5, 6, 7, 24,
|
||||
9, 10, 11, 28,
|
||||
13, 14, 15, 16,
|
||||
17, 18, 19, 20,
|
||||
21, 22, 23, 24,
|
||||
25, 26, 27, 28,
|
||||
29, 30, 31,
|
||||
];
|
||||
|
||||
// mirror memory should have the same data
|
||||
for (idx, addr) in (0x3F20 .. 0x3F3F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(expected[idx], mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3F40 .. 0x3F5F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(expected[idx], mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3F60 .. 0x3F7F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(expected[idx], mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3F80 .. 0x3F9F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(expected[idx], mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3FA0 .. 0x3FBF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(expected[idx], mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3FC0 .. 0x3FDF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(expected[idx], mem.readb_ppu(addr));
|
||||
}
|
||||
for (idx, addr) in (0x3FE0 .. 0x3FFF + 1).enumerate() {
|
||||
assert_eq!(idx as Byte, mem.readb_ppu(addr));
|
||||
assert_eq!(expected[idx], mem.readb_ppu(addr));
|
||||
}
|
||||
|
||||
// write data to mirrored addr range
|
||||
for (idx, addr) in (0x3FC0 .. 0x3FDF + 1).enumerate() {
|
||||
mem.writeb_ppu(addr, idx as Byte + 1);
|
||||
mem.writeb_ppu(addr, expected[idx] + 2);
|
||||
}
|
||||
|
||||
// start memory should have the same data
|
||||
for (idx, addr) in (0x3F00 .. 0x3F1F + 1).enumerate() {
|
||||
assert_eq!(idx as Byte + 1, mem.readb_ppu(addr));
|
||||
assert_eq!(expected[idx] + 2, mem.readb_ppu(addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user