ppu draw in lines + fps
This commit is contained in:
64
src/main.rs
64
src/main.rs
@@ -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,18 +48,24 @@ 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 {
|
||||
@@ -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() {
|
||||
|
||||
37
src/nes.rs
37
src/nes.rs
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
153
src/nes/ppu.rs
153
src/nes/ppu.rs
@@ -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!() },
|
||||
_ => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user