ppu main window drawing
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -8,11 +8,13 @@ dependencies = [
|
||||
"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)",
|
||||
"image 0.22.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"phf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"piston2d-opengl_graphics 0.70.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"piston_window 0.105.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"simple_logger 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
||||
@@ -18,3 +18,5 @@ piston2d-opengl_graphics = "0.70.*"
|
||||
gfx_core = "0.9.2"
|
||||
gfx_device_gl = "0.16.*"
|
||||
gfx_glyph = "0.16.*"
|
||||
image = "0.22.*"
|
||||
rand = "0.7.2"
|
||||
90
src/main.rs
90
src/main.rs
@@ -1,14 +1,17 @@
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
extern crate image;
|
||||
extern crate simple_logger;
|
||||
extern crate phf;
|
||||
extern crate piston_window;
|
||||
extern crate rand;
|
||||
|
||||
mod nes;
|
||||
|
||||
use std::path::Path;
|
||||
use piston_window::*;
|
||||
use nes::NES;
|
||||
use nes::types::*;
|
||||
use nes::cpu::*;
|
||||
use nes::bus::*;
|
||||
@@ -21,7 +24,7 @@ use failure::Error;
|
||||
use gfx_glyph::{Section, GlyphBrushBuilder,GlyphBrush, Scale};
|
||||
use gfx_device_gl::{Resources,Factory};
|
||||
|
||||
const BG_COLOR: [f32; 4] = [0.0, 0.0, 252.0/256.0, 1.0];
|
||||
const BG_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
|
||||
|
||||
// font options
|
||||
const FT_SIZE_PX: f32 = 11.0;
|
||||
@@ -36,51 +39,41 @@ lazy_static! {
|
||||
fn main() -> Result<(), Error> {
|
||||
simple_logger::init_with_level(Level::Info).unwrap();
|
||||
|
||||
let mut bus = MemoryBus::new();
|
||||
let mut nes = NES::new();
|
||||
let cartridge = Path::new("test_roms/nestest.nes");
|
||||
let cartridge = Cartridge::new(cartridge)?;
|
||||
bus.insert_cartrige(cartridge);
|
||||
|
||||
// Load little test program into ram
|
||||
// let test_prog: [Byte; 28] = [0xA2, 0x0A, 0x8E, 0x00, 0x00, 0xA2, 0x03, 0x8E, 0x01, 0x00, 0xAC, 0x00, 0x00, 0xA9,
|
||||
// 0x00, 0x18, 0x6D, 0x01, 0x00, 0x88, 0xD0, 0xFA, 0x8D, 0x02, 0x00, 0xEA, 0xEA, 0xEA];
|
||||
// let offset: Addr = 0x8000;
|
||||
// // craft test cartrige
|
||||
// let mut cart = Cartridge::dummy();
|
||||
// bus.insert_cartrige(cart);
|
||||
// for i in 0..test_prog.len() {
|
||||
// let addr = (i as Addr) + offset;
|
||||
// bus.writeb(addr, test_prog[i])
|
||||
// }
|
||||
// // hint program location to processor
|
||||
// bus.writew(0xfffc, offset);
|
||||
nes.insert_cartrige(cartridge);
|
||||
nes.cpu.find_pc_addr(&nes.bus);
|
||||
nes.cpu.regs.pc = 0xC000;
|
||||
|
||||
// disassemble instructions
|
||||
let disasm = Disasm::disassemble(&bus, 0xC000, 0xFFFF).unwrap();
|
||||
// println!("{:?}", disasm.instructions);
|
||||
// return;
|
||||
// let disasm = Disasm::disassemble(&[], 0x000).unwrap();
|
||||
|
||||
// Create and reset CPU
|
||||
let mut cpu: CPU = CPU::new();
|
||||
cpu.find_pc_addr(&bus);
|
||||
cpu.regs.pc = 0xC000;
|
||||
// return Ok(());
|
||||
// cpu.reset(&bus);
|
||||
let disasm = Disasm::disassemble(&nes.bus, 0xC000, 0xFFFF).unwrap();
|
||||
|
||||
// Prepare window and drawing resources
|
||||
let mut window: PistonWindow = WindowSettings::new("NESemu", [256*3, 240*2])
|
||||
let mut window: PistonWindow = WindowSettings::new("xXx NESemu xXx", [256*3, 240*2+50])
|
||||
.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);
|
||||
// let mut glyphs = get_glyphs(&mut window);
|
||||
|
||||
// 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)
|
||||
.initial_cache_size((1024, 1024))
|
||||
.build(window.factory.clone());
|
||||
|
||||
// main screen texture
|
||||
let mut texture_ctx = TextureContext {
|
||||
factory: window.factory.clone(),
|
||||
encoder: window.factory.create_command_buffer().into(),
|
||||
};
|
||||
let mut texture: G2dTexture = Texture::from_image(
|
||||
&mut texture_ctx,
|
||||
&nes.ppu.canvas_main,
|
||||
&TextureSettings::new()
|
||||
).unwrap();
|
||||
|
||||
|
||||
// Main loop
|
||||
let mut run = true;
|
||||
@@ -88,22 +81,29 @@ fn main() -> Result<(), Error> {
|
||||
if let Some(_) = event.update_args() {
|
||||
// if cpu.regs.pc == 0xC6A2 { run = false }
|
||||
if run {
|
||||
cpu.clock(&mut bus);
|
||||
nes.clock();
|
||||
}
|
||||
}
|
||||
if let Some(_) = event.render_args() {
|
||||
render(&mut window, &event, &mut glyph_brush, &cpu, &bus, &disasm);
|
||||
texture.update(&mut texture_ctx, &nes.ppu.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);
|
||||
image(&texture, transform, g);
|
||||
});
|
||||
render_debug(&mut window, &event, &mut glyph_brush, &nes.cpu, &nes.bus, &disasm);
|
||||
}
|
||||
if let Some(Button::Keyboard(key)) = event.press_args() {
|
||||
match key {
|
||||
Key::C => cpu.clock(&mut bus), // advance one clock
|
||||
Key::C => nes.clock(), // advance one clock
|
||||
Key::S => { // advance to next instruction
|
||||
cpu.clock(&mut bus);
|
||||
while cpu.is_ahead() {
|
||||
cpu.clock(&mut bus);
|
||||
nes.clock();
|
||||
while nes.cpu.is_ahead() {
|
||||
nes.clock();
|
||||
}
|
||||
}
|
||||
Key::R => cpu.reset(&mut bus),
|
||||
Key::R => nes.cpu.reset(&mut nes.bus),
|
||||
Key::Space => run = !run,
|
||||
Key::Up => {
|
||||
event_settings.ups = (event_settings.ups as f64 * 1.1) as u64 + 1;
|
||||
@@ -122,24 +122,22 @@ fn main() -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render(window: &mut PistonWindow, event: &Event,
|
||||
fn render_debug(window: &mut PistonWindow, event: &Event,
|
||||
glyphs: &mut GlyphBrush<Resources, Factory>,
|
||||
cpu: &CPU, bus: &MemoryBus, disasm: &Disasm) {
|
||||
window.draw_2d(event, |c, g, d| {
|
||||
clear(BG_COLOR, g);
|
||||
cpu: &CPU, _bus: &Bus, 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_memory(glyphs, bus,
|
||||
[debug_offset[0] + 400.0, debug_offset[1]]);
|
||||
|
||||
|
||||
// render_memory(glyphs, bus,
|
||||
// [debug_offset[0] + 400.0, debug_offset[1]]);
|
||||
});
|
||||
glyphs.use_queue().draw(&mut window.encoder, &window.output_color).unwrap();
|
||||
window.encoder.flush(&mut window.device);
|
||||
|
||||
}
|
||||
|
||||
fn render_cpu(glyphs: &mut GlyphBrush<Resources, Factory>, cpu: &CPU, offset: [f32; 2]) {
|
||||
@@ -291,7 +289,7 @@ fn render_disasm(glyphs: &mut GlyphBrush<Resources, Factory>,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_memory(glyphs: &mut GlyphBrush<Resources, Factory>, bus: &MemoryBus, offset: [f32; 2]) {
|
||||
fn render_memory(glyphs: &mut GlyphBrush<Resources, Factory>, bus: &Bus, offset: [f32; 2]) {
|
||||
let mut position_y = (offset[0], offset[1]);
|
||||
for page in (0x0000..0x00FF).step_by(16) {
|
||||
position_y.1 += FT_LINE_DISTANCE + FT_SIZE_PX;
|
||||
|
||||
39
src/nes.rs
39
src/nes.rs
@@ -1,3 +1,8 @@
|
||||
use crate::nes::bus::*;
|
||||
use crate::nes::cartridge::Cartridge;
|
||||
use crate::nes::ppu::*;
|
||||
use crate::nes::cpu::*;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub mod cpu;
|
||||
pub mod bus;
|
||||
@@ -5,3 +10,37 @@ pub mod types;
|
||||
pub mod disasm;
|
||||
pub mod cartridge;
|
||||
pub mod mappers;
|
||||
pub mod ppu;
|
||||
|
||||
|
||||
pub struct NES {
|
||||
pub bus: Bus,
|
||||
pub cpu: CPU,
|
||||
pub ppu: PPU,
|
||||
pub clock_count: u64,
|
||||
}
|
||||
|
||||
impl NES {
|
||||
pub fn new() -> Self {
|
||||
NES {
|
||||
bus: Bus::new(),
|
||||
cpu: CPU::new(),
|
||||
ppu: PPU::new(),
|
||||
clock_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_cartrige(&mut self, cartrige: Cartridge) {
|
||||
self.bus.insert_cartrige(cartrige);
|
||||
}
|
||||
|
||||
pub fn clock(&mut self) {
|
||||
self.clock_count += 1;
|
||||
if self.clock_count % 3 == 0 {
|
||||
self.cpu.clock(&mut self.bus);
|
||||
}
|
||||
self.ppu.clock(&mut self.bus);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ use crate::nes::cartridge::Cartridge;
|
||||
|
||||
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];
|
||||
|
||||
// Generic interface for a device allowing to read/write memory
|
||||
@@ -20,6 +22,21 @@ pub trait Memory {
|
||||
}
|
||||
}
|
||||
|
||||
// PPU interface to allow read/write of memory
|
||||
pub trait PPUMemory {
|
||||
fn readb_ppu(&self, addr: Addr) -> Byte;
|
||||
fn writeb_ppu(&mut self, addr: Addr, data: Byte);
|
||||
fn readw_ppu(&self, addr: Addr) -> Word {
|
||||
let lo = self.readb_ppu(addr);
|
||||
let hi = self.readb_ppu(addr+1);
|
||||
(hi as Word) << 8 | lo as Word
|
||||
}
|
||||
fn writew_ppu(&mut self, addr: Addr, data: Word) {
|
||||
self.writeb_ppu(addr, data as Byte);
|
||||
self.writeb_ppu(addr + 1, (data >> 8) as Byte);
|
||||
}
|
||||
}
|
||||
|
||||
// Impl by devices to access the Bus
|
||||
pub trait BusDevice {
|
||||
fn readb<T: Memory>(&self, bus: &T, addr: Addr) -> Byte {
|
||||
@@ -35,6 +52,21 @@ pub trait BusDevice {
|
||||
}
|
||||
}
|
||||
|
||||
// Impl by devices to access the Bus
|
||||
pub trait PPUBusDevice {
|
||||
fn readb<T: PPUMemory>(&self, bus: &T, addr: Addr) -> Byte {
|
||||
bus.readb_ppu(addr)
|
||||
}
|
||||
|
||||
fn readw<T: PPUMemory>(&self, bus: &T, addr: Addr) -> Word {
|
||||
bus.readw_ppu(addr)
|
||||
}
|
||||
|
||||
fn writeb<T: PPUMemory>(&mut self, bus: &mut T, addr: Addr, data: Byte) {
|
||||
bus.writeb_ppu(addr, data)
|
||||
}
|
||||
}
|
||||
|
||||
// Impl by devices to do stuff on bus clock
|
||||
pub trait Clockable {
|
||||
fn clock<T: Memory>(&mut self, bus: &mut T);
|
||||
@@ -43,14 +75,14 @@ pub trait Clockable {
|
||||
|
||||
// A simple bus giving access to a chunk of memory
|
||||
// and the cartrige
|
||||
pub struct MemoryBus {
|
||||
pub struct Bus {
|
||||
ram: [Byte; 0x0800], // 2kb
|
||||
cartrige: Option<Cartridge>
|
||||
}
|
||||
|
||||
impl MemoryBus {
|
||||
pub fn new() -> MemoryBus {
|
||||
MemoryBus {
|
||||
impl Bus {
|
||||
pub fn new() -> Bus {
|
||||
Bus {
|
||||
ram: [0; 0x0800],
|
||||
cartrige: None,
|
||||
}
|
||||
@@ -61,7 +93,7 @@ impl MemoryBus {
|
||||
}
|
||||
}
|
||||
|
||||
impl Memory for MemoryBus {
|
||||
impl Memory for Bus {
|
||||
|
||||
fn readb(&self, addr: Addr) -> Byte {
|
||||
if let Some(cartrige) = &self.cartrige {
|
||||
@@ -70,6 +102,42 @@ impl Memory for MemoryBus {
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
0x0000 // generic response
|
||||
}
|
||||
|
||||
fn writeb(&mut self, addr: Addr, data: Byte) {
|
||||
if let Some(cartrige) = &mut self.cartrige {
|
||||
if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] {
|
||||
cartrige.writeb(addr, data)
|
||||
}
|
||||
}
|
||||
if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PPUMemory for Bus {
|
||||
fn readb_ppu(&self, addr: Addr) -> Byte {
|
||||
if let Some(cartrige) = &self.cartrige {
|
||||
if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] {
|
||||
return cartrige.readb(addr)
|
||||
}
|
||||
}
|
||||
|
||||
if RAM_ADDR_RANGE[0] <= addr && addr <= RAM_ADDR_RANGE[1] {
|
||||
// Ram is 3x mirrored after 07ff
|
||||
return self.ram[(addr & 0x07ff) as usize]
|
||||
@@ -77,7 +145,7 @@ impl Memory for MemoryBus {
|
||||
0x0000 // generic response
|
||||
}
|
||||
|
||||
fn writeb(&mut self, addr: Addr, data: Byte) {
|
||||
fn writeb_ppu(&mut self, addr: Addr, data: Byte) {
|
||||
if let Some(cartrige) = &mut self.cartrige {
|
||||
if CART_ADDR_RANGE[0] <= addr && addr <= CART_ADDR_RANGE[1] {
|
||||
cartrige.writeb(addr, data)
|
||||
@@ -105,7 +173,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_read_write_ram() {
|
||||
let mut bus = MemoryBus::new();
|
||||
let mut bus = Bus::new();
|
||||
|
||||
// read/write to ram addr
|
||||
bus.writeb(0x0000, 1);
|
||||
@@ -120,7 +188,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_read_write_cartrige() {
|
||||
let mut bus = MemoryBus::new();
|
||||
let mut bus = Bus::new();
|
||||
|
||||
// read/write non-existant to cartrige
|
||||
bus.writeb(0x4030, 3);
|
||||
|
||||
@@ -17,7 +17,7 @@ impl BusDevice for Disasm {}
|
||||
|
||||
impl Disasm {
|
||||
// Disassemble given code region
|
||||
pub fn disassemble(mem: &MemoryBus, start: Addr, stop: Addr) -> Result<Self, Error> {
|
||||
pub fn disassemble(mem: &Bus, start: Addr, stop: Addr) -> Result<Self, Error> {
|
||||
let mut instructions = Vec::new();
|
||||
let mut addresses = Vec::new();
|
||||
let mut mem_iter = ((start as usize) .. (stop as usize)+1).map({|a| a as Addr});
|
||||
|
||||
30
src/nes/ppu.rs
Normal file
30
src/nes/ppu.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::nes::bus::Memory;
|
||||
use crate::nes::bus::Clockable;
|
||||
use image::{ImageBuffer, Rgba};
|
||||
use rand::Rng;
|
||||
|
||||
pub struct PPU {
|
||||
pub canvas_main: ImageBuffer<Rgba<u8>, Vec<u8>>
|
||||
}
|
||||
|
||||
impl PPU {
|
||||
pub fn new() -> Self {
|
||||
PPU {
|
||||
canvas_main: ImageBuffer::new(256, 240),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clockable for PPU {
|
||||
fn clock<T: Memory>(&mut self, bus: &mut T) {
|
||||
// update random pixel
|
||||
let mut rng = rand::thread_rng();
|
||||
let x = rng.gen_range(0, self.canvas_main.width());
|
||||
let y = rng.gen_range(0, self.canvas_main.height());
|
||||
let rgba = Rgba([rng.gen_range(0,255), rng.gen_range(0,255), rng.gen_range(0,255), 255]);
|
||||
self.canvas_main.put_pixel(x, y, rgba);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user