ppu draw in lines + fps

This commit is contained in:
Daniel Bauer
2020-01-05 12:57:31 +01:00
parent 47043b42a2
commit b3395b0f32
7 changed files with 241 additions and 61 deletions

7
Cargo.lock generated
View File

@@ -5,6 +5,7 @@ name = "NESemu"
version = "0.1.0"
dependencies = [
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"fps_counter 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_core 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_device_gl 0.16.2 (registry+https://github.com/rust-lang/crates.io-index)",
"gfx_glyph 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -324,6 +325,11 @@ name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fps_counter"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "getrandom"
version = "0.1.13"
@@ -1547,6 +1553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
"checksum fps_counter 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e87181dcb1c9f3297845af26d5d78c4936ff12f8abe1c2a5507459d45f5236a6"
"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
"checksum gfx 0.18.2 (registry+https://github.com/rust-lang/crates.io-index)" = "01de46f9508a5c259aef105f0bff760ceddca832ea9c87ce03d1923e22ee155b"
"checksum gfx_core 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "75fbddaef2e12b4995900539d7209d947b988a3d87ee8737484d049b526e5441"

View File

@@ -20,3 +20,4 @@ gfx_device_gl = "0.16.*"
gfx_glyph = "0.16.*"
image = "0.22.*"
rand = "0.7.2"
fps_counter = "1.0.0"

View File

@@ -6,6 +6,7 @@ extern crate simple_logger;
extern crate phf;
extern crate piston_window;
extern crate rand;
extern crate fps_counter;
mod nes;
@@ -17,7 +18,7 @@ use nes::disasm::*;
use opengl_graphics::OpenGL;
use log::Level;
use failure::Error;
use fps_counter::FPSCounter;
use gfx_glyph::{Section, GlyphBrushBuilder,GlyphBrush, Scale};
use gfx_device_gl::{Resources,Factory};
@@ -34,7 +35,7 @@ lazy_static! {
}
fn main() -> Result<(), Error> {
simple_logger::init_with_level(Level::Info).unwrap();
simple_logger::init_with_level(Level::Warn).unwrap();
let mut nes = NES::new();
let cartridge = Path::new("test_roms/nestest.nes");
@@ -47,19 +48,25 @@ fn main() -> Result<(), Error> {
let disasm = Disasm::disassemble(&nes.memory, 0xC000, 0xFFFF).unwrap();
// Prepare window and drawing resources
let mut window: PistonWindow = WindowSettings::new("xXx NESemu xXx", [256*3, 240*2+50])
// debugger + scaled nes resolution + border
let window_width = 300 + 256 * 2 + 5;
let window_height = 40 + 240*2 + 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();
event_settings.max_fps = 60;
event_settings.ups = 107400;
let mut events = Events::new(event_settings);
// font for debug screens
let font: &[u8] = include_bytes!("../resources/fonts/PressStart2P.ttf");
let mut glyph_brush: GlyphBrush<Resources, Factory> = GlyphBrushBuilder::using_font_bytes(font)
let mut glyphs: GlyphBrush<Resources, Factory> = GlyphBrushBuilder::using_font_bytes(font)
.initial_cache_size((1024, 1024))
.build(window.factory.clone());
// fps counter
let mut fps = FPSCounter::new();
// main screen texture
let mut texture_ctx = TextureContext {
factory: window.factory.clone(),
@@ -67,7 +74,7 @@ fn main() -> Result<(), Error> {
};
let mut texture: G2dTexture = Texture::from_image(
&mut texture_ctx,
&nes.ppu.canvas_main,
&nes.ppu.borrow().canvas_main,
&TextureSettings::new()
).unwrap();
@@ -75,31 +82,37 @@ fn main() -> Result<(), Error> {
// Main loop
let mut run = true;
while let Some(event) = events.next(&mut window) {
if let Some(_) = event.update_args() {
// if cpu.regs.pc == 0xC6A2 { run = false }
if run {
nes.clock();
}
}
if let Some(_) = event.render_args() {
texture.update(&mut texture_ctx, &nes.ppu.canvas_main).unwrap();
// Run enough clocks to render the next frame
if run { nes.clock_frame(); }
texture.update(&mut texture_ctx, &nes.ppu.borrow().canvas_main).unwrap();
window.draw_2d(&event, |c, g, d| {
clear(BG_COLOR, g);
texture_ctx.encoder.flush(d);
let transform = c.transform.trans(256.0, 50.0).scale(2.0, 2.0);
let transform = c.transform.trans(300.0, 40.0).scale(2.0, 2.0);
image(&texture, transform, g);
// fps
let fps = fps.tick();
let position = (410.0, 10.0 + FT_SIZE_PX);
let color = if fps < 60 { FT_COLOR_RED } else { FT_COLOR_WHITE };
glyphs.queue(Section {
text: &format!("FPS: {}", fps),
scale: *FT_SCALE,
screen_position: position,
color: color,
..Section::default()
});
});
render_debug(&mut window, &event, &mut glyph_brush, &nes.cpu, &nes, &disasm);
render_debug(&mut window, &event, &mut glyphs, &nes, &disasm);
}
if let Some(Button::Keyboard(key)) = event.press_args() {
match key {
Key::C => nes.clock(), // advance one clock
Key::S => { // advance to next instruction
nes.clock();
while nes.cpu.is_ahead() {
nes.clock();
}
}
Key::S => { nes.clock_instruction() }
Key::F => { nes.clock_frame() }
Key::R => nes.reset(),
Key::Space => run = !run,
Key::Up => {
@@ -121,14 +134,14 @@ fn main() -> Result<(), Error> {
fn render_debug(window: &mut PistonWindow, event: &Event,
glyphs: &mut GlyphBrush<Resources, Factory>,
cpu: &CPU, _nes: &NES, disasm: &Disasm) {
nes: &NES, disasm: &Disasm) {
window.draw_2d(event, |_c, _g, _d| {
let debug_offset = [10.0, 10.0];
render_cpu(glyphs, cpu, debug_offset);
render_disasm(glyphs, disasm, cpu.regs.pc,
[debug_offset[0], debug_offset[1] + (7.0 * (FT_LINE_DISTANCE+FT_SIZE_PX))]);
render_cpu(glyphs, &nes.cpu, debug_offset);
render_disasm(glyphs, disasm, nes.cpu.regs.pc,
[debug_offset[0], debug_offset[1] + (8.0 * (FT_LINE_DISTANCE+FT_SIZE_PX))]);
// render_memory(glyphs, nes,
// [debug_offset[0] + 400.0, debug_offset[1]]);
});
@@ -236,6 +249,7 @@ fn render_cpu(glyphs: &mut GlyphBrush<Resources, Factory>, cpu: &CPU, offset: [f
&format!("Y: {0:#x} ({0})", cpu.regs.y),
&format!("Stack P: {0:#x} ({0})", cpu.regs.sp),
&format!("Program P: {:#x}", cpu.regs.pc),
&format!("#CPU Cycles: {}", cpu.cycles),
];
for (i, &text) in cpu_register_texts.iter().enumerate() {

View File

@@ -1,3 +1,5 @@
use core::cell::RefCell;
use std::rc::Rc;
pub use crate::nes::cartridge::Cartridge;
pub use crate::nes::ppu::PPU;
pub use crate::nes::cpu::CPU;
@@ -17,17 +19,23 @@ pub mod ppu;
// as the mediator between the different components and hold the RAM
pub struct NES {
pub cpu: CPU,
pub ppu: PPU,
pub ppu: Rc<RefCell<PPU>>,
pub memory: NESMemory,
pub clock_count: u64,
}
impl NES {
pub fn new() -> Self {
// The memory module needs access to some parts of the nes like the ppu
// to forward cpu reads to it. Because the NES also requires access
// to the PPU for clocking, we use shared ownership. Otherwise, all
// components except the CPU would have to live inside the memory
// instance, which is conceptually not what we are looking for.
let ppu = Rc::new(RefCell::new(PPU::new()));
NES {
cpu: CPU::new(),
ppu: PPU::new(),
memory: NESMemory::new(),
ppu: ppu.clone(),
memory: NESMemory::new(ppu.clone()),
clock_count: 0,
}
}
@@ -55,7 +63,28 @@ impl NES {
if self.clock_count % 3 == 0 {
self.cpu.clock(&mut self.memory);
}
self.ppu.clock(&mut self.memory);
self.ppu.borrow_mut().clock(&mut self.memory);
}
// clock until the next cpu instruction is run
pub fn clock_instruction(&mut self) {
if !self.cpu.is_ahead() {
while !self.cpu.is_ahead() {
self.clock();
}
}
while self.cpu.is_ahead() {
self.clock();
}
}
// clock until the next frame is ready
pub fn clock_frame(&mut self) {
while !self.ppu.borrow().frame_ready {
self.clock();
}
self.ppu.borrow_mut().frame_ready = false;
}
}

View File

@@ -56,7 +56,7 @@ impl Debug for Registers {
pub struct CPU {
pub regs: Registers,
curr_op: Byte, // current operation
cycles: u64, // number of clock clycles the CPU is ahead of global clock
pub cycles: u64, // number of clock clycles the CPU is ahead of global clock
cycles_ahead: u8,
stopped: bool,
}

View File

@@ -1,24 +1,29 @@
use crate::nes::ppu::PPU;
use std::rc::Rc;
use core::cell::RefCell;
use crate::nes::cartridge::Cartridge;
use crate::nes::types::*;
const RAM_SIZE: usize = 0x0800;
const RAM_ADDR_RANGE: [Addr; 2] = [0x0000, 0x1fff];
const RAM_PHYS_RANGE: [Addr; 2] = [0x0000, 0x07ff];
const PPU_ADDR_RANGE: [Addr; 2] = [0x2000, 0x3fff];
const PPU_PHYS_RANGE: [Addr; 2] = [0x2000, 0x2007];
const CART_ADDR_RANGE: [Addr; 2] = [0x4020, 0xffff];
pub const RAM_SIZE: usize = 0x0800;
pub const RAM_ADDR_RANGE: [Addr; 2] = [0x0000, 0x1fff];
pub const RAM_PHYS_RANGE: [Addr; 2] = [0x0000, 0x07ff];
pub const PPU_ADDR_RANGE: [Addr; 2] = [0x2000, 0x3fff];
pub const PPU_PHYS_RANGE: [Addr; 2] = [0x2000, 0x2007];
pub const CART_ADDR_RANGE: [Addr; 2] = [0x4020, 0xffff];
// NES memory: Contains data from RAM, cartridge...
pub struct NESMemory {
ram: [Byte; RAM_SIZE], // 2kb
cartridge: Option<Cartridge>,
ppu: Rc<RefCell<PPU>>,
}
impl NESMemory {
pub fn new() -> Self {
pub fn new(ppu: Rc<RefCell<PPU>>) -> Self {
NESMemory {
ram: [0; RAM_SIZE],
cartridge: None,
ppu: ppu,
}
}
@@ -48,15 +53,14 @@ impl Memory for NESMemory {
return cartridge.readb(addr)
}
}
if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] {
// Ram is 3x mirrored after 0x07ff
return self.ram[(addr & RAM_PHYS_RANGE[1]) as usize]
}
// if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] {
// // PPU memory is mirrored after 0x2007 to 0x3fff
// return self.ram[(addr & PPU_PHYS_RANGE[1]) as usize]
// }
if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] {
let ppu = self.ppu.borrow();
return ppu.readb(addr & PPU_PHYS_RANGE[1]);
}
0x0000 // generic response
}
@@ -70,10 +74,10 @@ impl Memory for NESMemory {
// Ram is 3x mirrored after 07ff
self.ram[(addr & RAM_PHYS_RANGE[1]) as usize] = data
}
// if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] {
// // PPU memory is mirrored after 0x2007 to 0x3fff
// self.ram[(addr & PPU_PHYS_RANGE[1]) as usize] = data
// }
if PPU_ADDR_RANGE[0] <= addr && addr <= PPU_ADDR_RANGE[1] {
let mut ppu = self.ppu.borrow_mut();
ppu.writeb(addr & PPU_PHYS_RANGE[1], data);
}
}
}

View File

@@ -1,11 +1,85 @@
use phf::{Map,phf_map};
use crate::nes::types::*;
use crate::nes::memory::Memory;
use image::{ImageBuffer, Rgba};
use rand::Rng;
static PALETTE: Map<u8, Rgba<u8>> = phf_map! {
// 0x00
0x00u8 => Rgba([84, 84, 84, 255]),
0x01u8 => Rgba([0, 30, 116, 255]),
0x02u8 => Rgba([8, 16, 144, 255]),
0x03u8 => Rgba([48, 0, 136, 255]),
0x04u8 => Rgba([68, 0, 100, 255]),
0x05u8 => Rgba([92, 0, 48, 255]),
0x06u8 => Rgba([84, 4, 0, 255]),
0x07u8 => Rgba([60, 24, 0, 255]),
0x08u8 => Rgba([32, 42, 0, 255]),
0x09u8 => Rgba([8, 58, 0, 255]),
0x0au8 => Rgba([0, 64, 0, 255]),
0x0bu8 => Rgba([0, 60, 0, 255]),
0x0cu8 => Rgba([0, 50, 60, 255]),
0x0du8 => Rgba([0, 0, 0, 255]),
0x0eu8 => Rgba([0, 0, 0, 255]),
0x0fu8 => Rgba([0, 0, 0, 255]),
// 0x10
0x10u8 => Rgba([152, 150, 152, 255]),
0x11u8 => Rgba([8, 76, 196, 255]),
0x12u8 => Rgba([48, 50, 236, 255]),
0x13u8 => Rgba([92, 30, 228, 255]),
0x14u8 => Rgba([136, 20, 176, 255]),
0x15u8 => Rgba([160, 20, 100, 255]),
0x16u8 => Rgba([152, 34, 32, 255]),
0x17u8 => Rgba([120, 60, 0, 255]),
0x18u8 => Rgba([84, 90, 0, 255]),
0x19u8 => Rgba([40, 114, 0, 255]),
0x1au8 => Rgba([8, 124, 0, 255]),
0x1bu8 => Rgba([0, 118, 40, 255]),
0x1cu8 => Rgba([0, 102, 120, 255]),
0x1du8 => Rgba([0, 0, 0, 255]),
0x1eu8 => Rgba([0, 0, 0, 255]),
0x1fu8 => Rgba([0, 0, 0, 255]),
// 0x20
0x20u8 => Rgba([236, 238, 236, 255]),
0x21u8 => Rgba([76, 154, 236, 255]),
0x22u8 => Rgba([120, 124, 236, 255]),
0x23u8 => Rgba([176, 98, 236, 255]),
0x24u8 => Rgba([228, 84, 236, 255]),
0x25u8 => Rgba([236, 88, 180, 255]),
0x26u8 => Rgba([236, 106, 100, 255]),
0x27u8 => Rgba([212, 136, 32, 255]),
0x28u8 => Rgba([160, 170, 0, 255]),
0x29u8 => Rgba([116, 196, 0, 255]),
0x2au8 => Rgba([76, 208, 32, 255]),
0x2bu8 => Rgba([56, 204, 108, 255]),
0x2cu8 => Rgba([56, 180, 204, 255]),
0x2du8 => Rgba([60, 60, 60, 255]),
0x2eu8 => Rgba([0, 0, 0, 255]),
0x2fu8 => Rgba([0, 0, 0, 255]),
// 0x30
0x30u8 => Rgba([236, 238, 236, 255]),
0x31u8 => Rgba([168, 204, 236, 255]),
0x32u8 => Rgba([188, 188, 236, 255]),
0x33u8 => Rgba([212, 178, 236, 255]),
0x34u8 => Rgba([236, 174, 236, 255]),
0x35u8 => Rgba([236, 174, 212, 255]),
0x36u8 => Rgba([236, 180, 176, 255]),
0x37u8 => Rgba([228, 196, 144, 255]),
0x38u8 => Rgba([204, 210, 120, 255]),
0x39u8 => Rgba([180, 222, 120, 255]),
0x3au8 => Rgba([168, 226, 144, 255]),
0x3bu8 => Rgba([152, 226, 180, 255]),
0x3cu8 => Rgba([160, 214, 228, 255]),
0x3du8 => Rgba([160, 162, 160, 255]),
0xe3u8 => Rgba([0, 0, 0, 255]),
0x3fu8 => Rgba([0, 0, 0, 255]),
};
pub struct PPU {
pub cycle: i16,
pub scanline: i16,
pub canvas_main: ImageBuffer<Rgba<u8>, Vec<u8>>
pub cycle: u16,
pub scanline: u16,
pub canvas_main: ImageBuffer<Rgba<u8>, Vec<u8>>,
pub frame_ready: bool
}
@@ -15,22 +89,73 @@ impl PPU {
cycle: 0,
scanline: 0,
canvas_main: ImageBuffer::new(256, 240),
frame_ready: false,
}
}
pub fn clock<T: Memory>(&mut self, _mem: &mut T) {
// random noise
let mut rng = rand::thread_rng();
let x = rng.gen_range(0, 256);
let y = rng.gen_range(0, 240);
if (self.cycle < self.canvas_main.width() as u16) &&
(self.scanline < self.canvas_main.height() as u16) {
// random noise
let mut rng = rand::thread_rng();
let x = self.cycle;
let y = self.scanline;
let color = rng.gen::<bool>();
let px = if color {
Rgba([255, 255, 255, 255])
} else {
Rgba([0, 0, 0, 255])
};
let color = rng.gen::<bool>();
let px = if color {
PALETTE[&0x0f]
} else {
PALETTE[&0x20]
};
self.canvas_main.put_pixel(x as u32, y as u32, px);
self.canvas_main.put_pixel(x as u32, y as u32, px);
}
// increment cycle/scanline
self.cycle += 1;
if self.cycle > 340 {
self.cycle = 0;
self.scanline += 1;
if self.scanline >= 261 {
self.scanline = 0;
self.frame_ready = true;
}
}
}
}
impl Memory for PPU {
fn readb(&self, addr: Addr) -> Byte {
// Only some of the PPU registers can actually by read
match addr {
// PPU status
0x2002 => { 0x00 },
// OAM data (object attribute memory = list of 64 sprites)
0x2004 => { unimplemented!() },
// ppu data
0x2007 => { unimplemented!() },
_ => 0x00
}
}
fn writeb(&mut self, addr: Addr, data: Byte) {
// Only some of the PPU registers can be written to
match addr {
// Control
0x2000 => { unimplemented!() },
// Mask
0x2001 => { unimplemented!() },
// OAM address
0x2003 => { unimplemented!() },
// OAM data
0x2004 => { unimplemented!() },
// Scroll
0x2005 => { unimplemented!() },
// Address
0x2006 => { unimplemented!() },
// ppu data
0x2007 => { unimplemented!() },
_ => { }
}
}
}