├── rustfmt.toml ├── app ├── src │ ├── lib.rs │ └── main.rs ├── Cargo.toml └── config │ └── default.toml ├── .gitmodules ├── .gitignore ├── src ├── audio │ ├── mod.rs │ └── sdl.rs ├── screen │ ├── mod.rs │ └── sdl.rs ├── util.rs ├── mappers │ ├── volatile.rs │ ├── battery.rs │ ├── mod.rs │ ├── bank.rs │ ├── mapper000.rs │ └── mmc1.rs ├── apu │ ├── dmc.rs │ ├── buffer.rs │ ├── triangle.rs │ ├── noise.rs │ ├── square.rs │ ├── components.rs │ └── mod.rs ├── io │ ├── mod.rs │ ├── sdl.rs │ └── fm2.rs ├── memory.rs ├── tests │ ├── hash_screen.rs │ ├── test_io.rs │ ├── bench.rs │ └── mod.rs ├── cart │ ├── mod.rs │ └── ines.rs ├── lib.rs ├── cpu │ ├── dispatcher.rs │ ├── nes_analyst.rs │ └── x86_64_compiler │ │ └── addressing_modes.rs └── ppu │ ├── ppu_memory.rs │ ├── ppu_reg.rs │ ├── background_rendering.rs │ ├── sprite_rendering.rs │ └── mod.rs ├── Cargo.toml ├── LICENSE ├── README.md └── Cargo.lock /rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Windows" 2 | wrap_comments = true 3 | reorder_imports = true 4 | -------------------------------------------------------------------------------- /app/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() {} 5 | } 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nes-test-roms"] 2 | path = nes-test-roms 3 | url = https://github.com/christopherpow/nes-test-roms.git 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /app/target/ 3 | .project 4 | /.settings/ 5 | /target-install/ 6 | /app/target-install/ 7 | **/*.bk 8 | output 9 | nestest* 10 | SDL2.dll 11 | /.cargo/ 12 | **/*fuse_hidden* 13 | callgrind.out* 14 | .vs 15 | .vscode -------------------------------------------------------------------------------- /app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "corrosion-app" 3 | version = "0.1.0" 4 | authors = ["Brook Heisler "] 5 | 6 | [features] 7 | debug_features = ["corrosion/debug_features"] 8 | 9 | [dependencies] 10 | corrosion = { path = "..", features = [] } 11 | stopwatch = "0.0.6" 12 | config = "0.6.0" 13 | 14 | [profile.release] 15 | #lto = true 16 | #debug = true 17 | -------------------------------------------------------------------------------- /src/audio/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod sdl; 2 | 3 | use apu::Sample; 4 | 5 | pub trait AudioOut { 6 | fn play(&mut self, buffer: &[Sample]); 7 | fn sample_rate(&self) -> f64; 8 | } 9 | 10 | pub struct DummyAudioOut; 11 | 12 | impl AudioOut for DummyAudioOut { 13 | fn play(&mut self, _: &[Sample]) {} 14 | fn sample_rate(&self) -> f64 { 15 | 44100.0 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/screen/mod.rs: -------------------------------------------------------------------------------- 1 | use ppu::{Color, SCREEN_BUFFER_SIZE}; 2 | 3 | pub mod sdl; 4 | 5 | pub trait Screen { 6 | fn draw(&mut self, buf: &[Color; SCREEN_BUFFER_SIZE]); 7 | } 8 | 9 | pub struct DummyScreen; 10 | 11 | impl Default for DummyScreen { 12 | fn default() -> DummyScreen { 13 | DummyScreen 14 | } 15 | } 16 | 17 | impl Screen for DummyScreen { 18 | fn draw(&mut self, _: &[Color; SCREEN_BUFFER_SIZE]) {} 19 | } 20 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | pub struct ShiftRegister8 { 2 | bits: u8, 3 | } 4 | 5 | impl ShiftRegister8 { 6 | pub fn new(init: u8) -> ShiftRegister8 { 7 | ShiftRegister8 { bits: init } 8 | } 9 | 10 | pub fn shift(&mut self) -> u8 { 11 | let result = self.bits & 0x01; 12 | self.bits >>= 1; 13 | result 14 | } 15 | 16 | pub fn load(&mut self, val: u8) { 17 | self.bits = val; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/mappers/volatile.rs: -------------------------------------------------------------------------------- 1 | use memory::MemSegment; 2 | 3 | pub struct VolatileRam { 4 | data: Box<[u8]>, 5 | } 6 | 7 | impl VolatileRam { 8 | pub fn new(size: usize) -> VolatileRam { 9 | VolatileRam { data: vec![0u8; size].into_boxed_slice() } 10 | } 11 | 12 | fn wrap_addr(&self, idx: u16) -> usize { 13 | let idx = idx as usize; 14 | idx % self.data.len() 15 | } 16 | } 17 | 18 | impl MemSegment for VolatileRam { 19 | fn read(&mut self, idx: u16) -> u8 { 20 | self.data[self.wrap_addr(idx)] 21 | } 22 | fn write(&mut self, idx: u16, val: u8) { 23 | self.data[self.wrap_addr(idx)] = val; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/apu/dmc.rs: -------------------------------------------------------------------------------- 1 | //! Contains structures used by the NES's DMC channel. 2 | 3 | use apu::Writable; 4 | 5 | #[allow(dead_code)] 6 | pub struct DMC { 7 | freq: u8, 8 | direct: u8, 9 | sample_addr: u8, 10 | sample_length: u8, 11 | } 12 | 13 | #[allow(unused_variables)] 14 | impl DMC { 15 | pub fn new() -> DMC { 16 | DMC { 17 | freq: 0, 18 | direct: 0, 19 | sample_addr: 0, 20 | sample_length: 0, 21 | } 22 | } 23 | 24 | pub fn play(&mut self, from_cyc: u32, to_cyc: u32) {} 25 | } 26 | 27 | #[allow(unused_variables)] 28 | impl Writable for DMC { 29 | fn write(&mut self, idx: u16, val: u8) {} 30 | } 31 | -------------------------------------------------------------------------------- /app/config/default.toml: -------------------------------------------------------------------------------- 1 | graphics_enabled = true 2 | sound_enabled = true 3 | 4 | # Enable the JIT compiler. Currently only available on x86_64. 5 | jit = true 6 | 7 | # These settings will be ignored unless the executable is compiled with the 8 | # debug_features feature. 9 | [debug] 10 | 11 | # Print to the console detailed information about the pixel underneath the mouse. 12 | # Useful for PPU debugging 13 | mousepick = false 14 | 15 | # Print to the console a detailed trace of every 6502 instruction executed by 16 | # the emulated CPU. Works in both JIT and interpreter mode. 17 | trace_cpu = false 18 | 19 | # Print to the console a disassembly of each block of 6502 code when it's compiled 20 | # by the JIT compiler. 21 | disassemble_functions = false 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "corrosion" 3 | version = "0.1.0" 4 | authors = ["Brook Heisler "] 5 | 6 | [features] 7 | default = [] 8 | vectorize = ["simd"] 9 | debug_features = [] 10 | 11 | [dependencies] 12 | bitflags = "0.9" 13 | quick-error = "1.2" 14 | sdl2 = "0.29" 15 | blip_buf = "0.1.4" 16 | memmap = "0.5" 17 | simd = { version = "0.2", optional = true } 18 | dynasm = "0.1.2" 19 | fnv = "1.0" 20 | 21 | [target.'cfg(target_arch = "x86_64")'.dependencies] 22 | dynasmrt = "0.1.1" 23 | 24 | [dependencies.nom] 25 | version = "3.2" 26 | features = ["nightly"] 27 | 28 | [dev-dependencies] 29 | sha1 = "0.2" 30 | rand = "0.3" 31 | 32 | [profile.bench] 33 | opt-level = 3 34 | debug = true 35 | lto = true 36 | -------------------------------------------------------------------------------- /src/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod sdl; 2 | pub mod fm2; 3 | 4 | use super::memory::MemSegment; 5 | 6 | /// Some bits of the controller reads return open bus garbage. Since the last 7 | /// byte on the bus is almost always 0x40, we can just use that as a constant 8 | /// for now. 9 | pub const OPEN_BUS: u8 = 0x40; 10 | 11 | pub trait IO: MemSegment { 12 | fn poll(&mut self); 13 | } 14 | 15 | pub enum DummyIO { 16 | Dummy, 17 | } 18 | 19 | impl DummyIO { 20 | pub fn new() -> DummyIO { 21 | DummyIO::Dummy 22 | } 23 | } 24 | 25 | impl MemSegment for DummyIO { 26 | fn read(&mut self, _: u16) -> u8 { 27 | 0 28 | } 29 | 30 | fn write(&mut self, _: u16, _: u8) { 31 | () 32 | } 33 | } 34 | 35 | impl IO for DummyIO { 36 | fn poll(&mut self) { 37 | () 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An NES emulator written in Rust as a hobby project. It only supports Mappers 0 and 1 at this point, but that's enough to play Donkey Kong, Super Mario Bros and Legend of Zelda. It also has a working (though rudimentary) just-in-time compiler targeting x86_64 machine code with [dynasm-rs](https://github.com/CensoredUsername/dynasm-rs). The JIT compiler is currently not well-optimized and has difficulty dealing with heavy bankswitching (eg. Legend of Zelda runs slower with JIT than without due to excessive recompilation) but I hope to improve on that in the future. 2 | 3 | ### Building 4 | 5 | In order to build corrosion, you will require a recent nightly version of Rust. Nightly is currently required in order to use dynasm-rs. You will also need to install [sdl2](https://github.com/AngryLawyer/rust-sdl2). Once ready, building corrosion is as simple as running `cargo build --release` in the `app` directory. 6 | 7 | There are a number of additional debug features. These must be compiled into the executable like so: 8 | 9 | cargo build --release --features debug_features 10 | 11 | Once compiled, they can be enabled by setting the flags in app/config/default.toml. See the config file for supported features. 12 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | #![macro_use] 2 | 3 | macro_rules! invalid_address { 4 | ($e:expr) => (panic!("Invalid NES Memory Address: {:X}", $e)); 5 | } 6 | 7 | use std::ops::Range; 8 | 9 | pub trait MemSegment { 10 | fn read(&mut self, idx: u16) -> u8; 11 | fn read_w(&mut self, idx: u16) -> u16 { 12 | let low = self.read(idx) as u16; 13 | let high = self.read(idx + 1) as u16; 14 | (high << 8) | low 15 | } 16 | 17 | fn write(&mut self, idx: u16, val: u8); 18 | fn write_w(&mut self, idx: u16, val: u16) { 19 | let low = (val & 0x00FF) as u8; 20 | let high = ((val & 0xFF00) >> 8) as u8; 21 | self.write(idx, low); 22 | self.write(idx + 1, high); 23 | } 24 | 25 | fn print(&mut self, range: Range) { 26 | self.print_columns(range, 16) 27 | } 28 | 29 | fn print_columns(&mut self, range: Range, columns: u16) { 30 | let lower = range.start / columns; 31 | let upper = (range.end + columns - 1) / columns; 32 | 33 | for y in lower..upper { 34 | print!("{:04X}: ", y * columns); 35 | for x in 0..columns { 36 | let addr = (y * columns) + x; 37 | print!("{:02X} ", self.read(addr)); 38 | if x % 4 == 3 { 39 | print!(" "); 40 | } 41 | } 42 | println!(""); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mappers/battery.rs: -------------------------------------------------------------------------------- 1 | use memmap::{Mmap, Protection}; 2 | use memory::MemSegment; 3 | use std::fs::OpenOptions; 4 | use std::io; 5 | use std::path::Path; 6 | 7 | pub struct BatteryBackedRam { 8 | file: Mmap, 9 | len: u32, 10 | } 11 | 12 | impl BatteryBackedRam { 13 | pub fn new(rom_path: &Path, size: u32) -> io::Result { 14 | let sav_path = rom_path.to_path_buf().with_extension("sav"); 15 | let file = try!( 16 | OpenOptions::new() 17 | .write(true) 18 | .read(true) 19 | .create(true) 20 | .open(sav_path) 21 | ); 22 | try!(file.set_len(size as u64)); 23 | let file = try!(Mmap::open(&file, Protection::ReadWrite)); 24 | Ok(BatteryBackedRam { 25 | file: file, 26 | len: size, 27 | }) 28 | } 29 | 30 | fn wrap_addr(&self, idx: u16) -> usize { 31 | let idx = idx as usize; 32 | idx % self.len as usize 33 | } 34 | 35 | fn slice(&mut self) -> &mut [u8] { 36 | unsafe { self.file.as_mut_slice() } 37 | } 38 | } 39 | 40 | impl MemSegment for BatteryBackedRam { 41 | fn read(&mut self, idx: u16) -> u8 { 42 | let addr = self.wrap_addr(idx); 43 | self.slice()[addr] 44 | } 45 | 46 | fn write(&mut self, idx: u16, val: u8) { 47 | let addr = self.wrap_addr(idx); 48 | self.slice()[addr] = val; 49 | self.file.flush_async().unwrap(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/tests/hash_screen.rs: -------------------------------------------------------------------------------- 1 | extern crate sha1; 2 | 3 | use self::sha1::{Digest, Sha1}; 4 | use ppu::{Color, SCREEN_BUFFER_SIZE}; 5 | use screen::Screen; 6 | use std::collections::HashMap; 7 | 8 | fn hash_screen(buf: &[Color; SCREEN_BUFFER_SIZE]) -> Digest { 9 | let newbuf: Vec = buf.iter().map(|col: &Color| col.bits()).collect(); 10 | 11 | let mut s = Sha1::new(); 12 | s.update(&newbuf); 13 | s.digest() 14 | } 15 | 16 | #[allow(dead_code)] 17 | pub struct HashPrinter { 18 | frames: u32, 19 | 20 | delegate: Box, 21 | } 22 | 23 | #[allow(dead_code)] 24 | impl HashPrinter { 25 | pub fn new(delegate: Box) -> HashPrinter { 26 | HashPrinter { 27 | frames: 0, 28 | delegate: delegate, 29 | } 30 | } 31 | } 32 | 33 | impl Screen for HashPrinter { 34 | fn draw(&mut self, buf: &[Color; SCREEN_BUFFER_SIZE]) { 35 | println!( 36 | "Frame: {}, Hash: {}", 37 | self.frames, 38 | hash_screen(buf).to_string() 39 | ); 40 | self.frames += 1; 41 | 42 | self.delegate.draw(buf); 43 | } 44 | } 45 | 46 | pub struct HashVerifier { 47 | hashes: HashMap, 48 | frames: u32, 49 | } 50 | 51 | impl HashVerifier { 52 | pub fn new(hashes: HashMap) -> HashVerifier { 53 | HashVerifier { 54 | frames: 0, 55 | hashes: hashes, 56 | } 57 | } 58 | } 59 | 60 | impl Screen for HashVerifier { 61 | fn draw(&mut self, buf: &[Color; SCREEN_BUFFER_SIZE]) { 62 | if self.hashes.contains_key(&self.frames) { 63 | assert_eq!( 64 | self.hashes.get(&self.frames).unwrap(), 65 | &hash_screen(buf).to_string() 66 | ); 67 | } 68 | self.frames += 1; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/tests/test_io.rs: -------------------------------------------------------------------------------- 1 | use io::IO; 2 | 3 | use io::OPEN_BUS; 4 | use memory::MemSegment; 5 | use std::collections::HashMap; 6 | use util::ShiftRegister8; 7 | 8 | pub struct TestIO { 9 | frames: u32, 10 | commands: HashMap, 11 | 12 | controller1: ShiftRegister8, 13 | controller2: ShiftRegister8, 14 | } 15 | 16 | // Commands are in the FM2 RLDUTSBA|RLDUTSBA format minus the commands block at 17 | // the start 18 | impl TestIO { 19 | pub fn new(commands: HashMap) -> TestIO { 20 | TestIO { 21 | frames: 0, 22 | commands: commands, 23 | 24 | controller1: ShiftRegister8::new(0), 25 | controller2: ShiftRegister8::new(0), 26 | } 27 | } 28 | } 29 | 30 | fn parse(string: &str) -> u8 { 31 | string.char_indices().filter(|&(_, c)| c != '.').fold( 32 | 0u8, 33 | |acc, 34 | (idx, 35 | _)| { 36 | acc | 1u8 << (7 - idx) 37 | }, 38 | ) 39 | } 40 | 41 | impl MemSegment for TestIO { 42 | fn read(&mut self, idx: u16) -> u8 { 43 | match idx { 44 | 0x4016 => OPEN_BUS | self.controller1.shift(), 45 | 0x4017 => OPEN_BUS | self.controller2.shift(), 46 | x => invalid_address!(x), 47 | } 48 | } 49 | 50 | fn write(&mut self, idx: u16, val: u8) { 51 | match idx { 52 | 0x4016 => { 53 | if val & 0x01 != 0 { 54 | if let Some(line) = self.commands.get(&self.frames) { 55 | let mut split = line.split('|'); 56 | self.controller1.load(parse(split.next().unwrap())); 57 | self.controller2.load(parse(split.next().unwrap())); 58 | } 59 | self.frames += 1; 60 | } 61 | } 62 | 0x4017 => (), 63 | x => invalid_address!(x), 64 | } 65 | } 66 | } 67 | 68 | impl IO for TestIO { 69 | fn poll(&mut self) { 70 | // Do nothing. 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/io/sdl.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use io::IO; 4 | use io::OPEN_BUS; 5 | use memory::MemSegment; 6 | use sdl2::EventPump; 7 | use sdl2::keyboard::KeyboardState; 8 | use sdl2::keyboard::Scancode; 9 | use std::cell::RefCell; 10 | 11 | use std::rc::Rc; 12 | use util::ShiftRegister8; 13 | 14 | const A: u8 = 1; 15 | const B: u8 = 1 << 1; 16 | const SELECT: u8 = 1 << 2; 17 | const START: u8 = 1 << 3; 18 | const UP: u8 = 1 << 4; 19 | const DOWN: u8 = 1 << 5; 20 | const LEFT: u8 = 1 << 6; 21 | const RIGHT: u8 = 1 << 7; 22 | 23 | pub struct SdlIO { 24 | event_pump: Rc>, 25 | controller1: ShiftRegister8, 26 | controller2: ShiftRegister8, 27 | } 28 | 29 | impl SdlIO { 30 | pub fn new(pump: Rc>) -> SdlIO { 31 | SdlIO { 32 | event_pump: pump, 33 | controller1: ShiftRegister8::new(0), 34 | controller2: ShiftRegister8::new(0), 35 | } 36 | } 37 | } 38 | 39 | impl MemSegment for SdlIO { 40 | fn read(&mut self, idx: u16) -> u8 { 41 | match idx { 42 | 0x4016 => OPEN_BUS | self.controller1.shift(), 43 | 0x4017 => OPEN_BUS | self.controller2.shift(), 44 | x => invalid_address!(x), 45 | } 46 | } 47 | 48 | fn write(&mut self, _: u16, _: u8) { 49 | // Do nothing 50 | } 51 | } 52 | 53 | fn read_key(state: &KeyboardState, key: Scancode, val: u8) -> u8 { 54 | if state.is_scancode_pressed(key) { 55 | val 56 | } else { 57 | 0 58 | } 59 | } 60 | 61 | impl IO for SdlIO { 62 | #[cfg_attr(rustfmt, rustfmt_skip)] 63 | fn poll(&mut self) { 64 | let pump_ref = self.event_pump.borrow(); 65 | let state = KeyboardState::new(&*pump_ref); 66 | 67 | let c1 = 68 | read_key(&state, Scancode::Z, A) | 69 | read_key(&state, Scancode::X, B) | 70 | read_key(&state, Scancode::Return, START) | 71 | read_key(&state, Scancode::Backspace, SELECT) | 72 | read_key(&state, Scancode::Up, UP) | 73 | read_key(&state, Scancode::Down, DOWN) | 74 | read_key(&state, Scancode::Right, RIGHT) | 75 | read_key(&state, Scancode::Left, LEFT); 76 | self.controller1.load(c1); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/io/fm2.rs: -------------------------------------------------------------------------------- 1 | use io::IO; 2 | 3 | use io::OPEN_BUS; 4 | use memory::MemSegment; 5 | use std::fs::File; 6 | use std::io::BufRead; 7 | use std::io::BufReader; 8 | use std::io::Result; 9 | use std::iter::Iterator; 10 | use util::ShiftRegister8; 11 | 12 | pub struct FM2IO { 13 | iter: Box>, 14 | controller1: ShiftRegister8, 15 | controller2: ShiftRegister8, 16 | } 17 | 18 | impl FM2IO { 19 | pub fn read(file: String) -> Result { 20 | let file = try!(File::open(file)); 21 | let reader = BufReader::new(file); 22 | let iter = reader 23 | .lines() 24 | .map(|result| result.unwrap()) 25 | .skip_while(|line| !line.contains('|')) 26 | .skip(1); 27 | Ok(FM2IO { 28 | iter: Box::new(iter), 29 | controller1: ShiftRegister8::new(0), 30 | controller2: ShiftRegister8::new(0), 31 | }) 32 | } 33 | } 34 | 35 | fn parse(string: &str) -> u8 { 36 | string.char_indices().filter(|&(_, c)| c != '.').fold( 37 | 0u8, 38 | |acc, 39 | (idx, 40 | _)| { 41 | acc | 1u8 << (7 - idx) 42 | }, 43 | ) 44 | } 45 | 46 | impl MemSegment for FM2IO { 47 | fn read(&mut self, idx: u16) -> u8 { 48 | match idx { 49 | 0x4016 => OPEN_BUS | self.controller1.shift(), 50 | 0x4017 => OPEN_BUS | self.controller2.shift(), 51 | x => invalid_address!(x), 52 | } 53 | } 54 | 55 | fn write(&mut self, idx: u16, val: u8) { 56 | match idx { 57 | 0x4016 => { 58 | if val & 0x01 != 0 { 59 | if let Some(line) = self.iter.next() { 60 | // Ignore the commands for now. 61 | let mut split = line.split('|').skip(1).skip(1); 62 | self.controller1.load(parse(split.next().unwrap())); 63 | self.controller2.load(parse(split.next().unwrap())); 64 | } 65 | } 66 | } 67 | 0x4017 => (), 68 | x => invalid_address!(x), 69 | } 70 | } 71 | } 72 | 73 | impl IO for FM2IO { 74 | fn poll(&mut self) { 75 | // Do nothing. 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/tests/bench.rs: -------------------------------------------------------------------------------- 1 | extern crate test; 2 | 3 | use self::test::Bencher; 4 | use Settings; 5 | use std::collections::HashMap; 6 | use std::path::Path; 7 | use tests::test_io; 8 | 9 | #[bench] 10 | fn bench_sprite(b: &mut Bencher) { 11 | run_benchmark( 12 | b, 13 | Path::new("nes-test-roms/other/SPRITE.NES"), 14 | HashMap::new(), 15 | Default::default(), 16 | ); 17 | } 18 | 19 | #[bench] 20 | fn bench_cpu_sprite_jit(b: &mut Bencher) { 21 | run_benchmark( 22 | b, 23 | Path::new("nes-test-roms/other/SPRITE.NES"), 24 | HashMap::new(), 25 | cpu_benchmark_settings(true), 26 | ); 27 | } 28 | 29 | #[bench] 30 | fn bench_cpu_sprite_no_jit(b: &mut Bencher) { 31 | run_benchmark( 32 | b, 33 | Path::new("nes-test-roms/other/SPRITE.NES"), 34 | HashMap::new(), 35 | cpu_benchmark_settings(false), 36 | ); 37 | } 38 | 39 | #[bench] 40 | fn bench_blocks(b: &mut Bencher) { 41 | run_benchmark( 42 | b, 43 | Path::new("nes-test-roms/other/BLOCKS.NES"), 44 | HashMap::new(), 45 | Default::default(), 46 | ); 47 | } 48 | 49 | #[bench] 50 | fn bench_cpu_blocks_jit(b: &mut Bencher) { 51 | run_benchmark( 52 | b, 53 | Path::new("nes-test-roms/other/BLOCKS.NES"), 54 | HashMap::new(), 55 | cpu_benchmark_settings(true), 56 | ); 57 | } 58 | 59 | #[bench] 60 | fn bench_cpu_blocks_no_jit(b: &mut Bencher) { 61 | run_benchmark( 62 | b, 63 | Path::new("nes-test-roms/other/BLOCKS.NES"), 64 | HashMap::new(), 65 | cpu_benchmark_settings(false), 66 | ); 67 | } 68 | 69 | pub fn cpu_benchmark_settings(jit: bool) -> Settings { 70 | Settings { 71 | jit: jit, 72 | graphics_enabled: false, 73 | sound_enabled: false, 74 | ..Default::default() 75 | } 76 | } 77 | 78 | pub fn run_benchmark( 79 | bencher: &mut Bencher, 80 | file_name: &Path, 81 | commands: HashMap, 82 | settings: Settings, 83 | ) { 84 | 85 | let cart = ::cart::Cart::read(file_name).expect("Failed to read ROM File"); 86 | let mut builder = ::EmulatorBuilder::new(cart, settings); 87 | builder.io = Box::new(test_io::TestIO::new(commands)); 88 | 89 | let mut emulator = builder.build(); 90 | 91 | while !emulator.rendering_enabled() { 92 | assert!(!emulator.halted()); 93 | emulator.run_frame(); 94 | } 95 | 96 | bencher.iter(|| { 97 | assert!(!emulator.halted()); 98 | emulator.run_frame(); 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /src/apu/buffer.rs: -------------------------------------------------------------------------------- 1 | //! Thin layer over `BlipBuf` which provides a slightly more convenient 2 | //! interface. 3 | 4 | use apu::Sample; 5 | use blip_buf::BlipBuf; 6 | use std::cell::RefCell; 7 | use std::rc::Rc; 8 | 9 | const NES_CLOCK_RATE: u64 = 1789773; 10 | const NES_FPS: usize = 60; 11 | const FRAMES_PER_BUFFER: usize = 1; 12 | 13 | pub struct SampleBuffer { 14 | blip: BlipBuf, 15 | samples: Vec, 16 | transfer_samples: u32, 17 | } 18 | 19 | /// Blip Buffer combined with a Vec to store the samples transferred out of the 20 | /// buffer, so we don't have to either allocate memory every transfer. 21 | impl SampleBuffer { 22 | pub fn new(out_rate: f64) -> SampleBuffer { 23 | let samples_per_frame = out_rate as u32 / NES_FPS as u32; 24 | let transfer_samples = samples_per_frame * FRAMES_PER_BUFFER as u32; 25 | 26 | let mut buf = BlipBuf::new(transfer_samples); 27 | buf.set_rates(NES_CLOCK_RATE as f64, out_rate); 28 | let samples = vec![0; (transfer_samples) as usize]; 29 | 30 | SampleBuffer { 31 | blip: buf, 32 | samples: samples, 33 | transfer_samples: transfer_samples, 34 | } 35 | } 36 | 37 | pub fn read(&mut self) -> &[Sample] { 38 | let samples_read = self.blip.read_samples(&mut self.samples, false); 39 | let slice: &[Sample] = &self.samples; 40 | &slice[0..samples_read] 41 | } 42 | 43 | pub fn add_delta(&mut self, clock_time: u32, delta: i32) { 44 | self.blip.add_delta(clock_time, delta) 45 | } 46 | 47 | pub fn end_frame(&mut self, clock_duration: u32) { 48 | self.blip.end_frame(clock_duration) 49 | } 50 | 51 | pub fn clocks_needed(&self) -> u32 { 52 | self.blip.clocks_needed(self.transfer_samples) 53 | } 54 | } 55 | 56 | /// Allows multiple channels to share a `SampleBuffer` but maintain separate 57 | /// waveforms. 58 | pub struct Waveform { 59 | buffer: Rc>, 60 | last_amp: Sample, 61 | volume_mult: i32, 62 | } 63 | 64 | impl Waveform { 65 | pub fn new(buffer: Rc>, volume_mult: i32) -> Waveform { 66 | Waveform { 67 | buffer: buffer, 68 | last_amp: 0, 69 | volume_mult: volume_mult, 70 | } 71 | } 72 | 73 | pub fn set_amplitude(&mut self, amp: Sample, cycle: u32) { 74 | let last_amp = self.last_amp; 75 | let delta = (amp - last_amp) as i32; 76 | if delta == 0 { 77 | return; 78 | } 79 | self.buffer.borrow_mut().add_delta( 80 | cycle, 81 | delta * self.volume_mult, 82 | ); 83 | self.last_amp = amp; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/mappers/mod.rs: -------------------------------------------------------------------------------- 1 | mod volatile; 2 | mod battery; 3 | 4 | mod bank; 5 | 6 | mod mapper000; 7 | mod mmc1; 8 | 9 | use cart::ScreenMode; 10 | pub use mappers::bank::RomBank; 11 | use std::path::Path; 12 | 13 | static VERTICAL: [u16; 4] = [0x2000, 0x2400, 0x2000, 0x2400]; 14 | static HORIZONTAL: [u16; 4] = [0x2000, 0x2000, 0x2400, 0x2400]; 15 | static ONE_SCREEN_LOW: [u16; 4] = [0x2000, 0x2000, 0x2000, 0x2000]; 16 | static ONE_SCREEN_HIGH: [u16; 4] = [0x2400, 0x2400, 0x2400, 0x2400]; 17 | static FOUR_SCREEN: [u16; 4] = [0x2000, 0x2400, 0x2800, 0x2C00]; 18 | 19 | fn standard_mapping_tables(mode: ScreenMode) -> &'static [u16; 4] { 20 | match mode { 21 | ScreenMode::Vertical => &VERTICAL, 22 | ScreenMode::Horizontal => &HORIZONTAL, 23 | ScreenMode::OneScreenHigh => &ONE_SCREEN_HIGH, 24 | ScreenMode::OneScreenLow => &ONE_SCREEN_LOW, 25 | ScreenMode::FourScreen => &FOUR_SCREEN, 26 | } 27 | } 28 | 29 | #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] 30 | pub struct RomAddress { 31 | pub window_id: usize, 32 | pub offset: u16, 33 | } 34 | 35 | pub trait Mapper { 36 | fn prg_rom_read(&mut self, idx: u16) -> &RomBank; 37 | fn prg_rom_write(&mut self, idx: u16, val: u8) -> &mut RomBank; 38 | 39 | /// Returns a struct which uniquely identifies the ROM cell backing the 40 | /// given address. 41 | fn prg_rom_address(&self, idx: u16) -> RomAddress; 42 | 43 | fn prg_ram_read(&mut self, idx: u16) -> u8; 44 | fn prg_ram_write(&mut self, idx: u16, val: u8); 45 | 46 | fn chr_read(&mut self, idx: u16) -> u8; 47 | fn chr_write(&mut self, idx: u16, val: u8); 48 | 49 | fn get_mirroring_table(&self) -> &[u16; 4]; 50 | } 51 | 52 | pub struct MapperParams<'a> { 53 | pub prg_rom: Vec, 54 | pub chr_rom: Vec, 55 | 56 | pub prg_ram_size: usize, 57 | 58 | pub rom_path: &'a Path, 59 | 60 | pub has_battery_backed_ram: bool, 61 | pub mirroring_mode: ScreenMode, 62 | } 63 | 64 | impl<'a> MapperParams<'a> { 65 | #[cfg(test)] 66 | pub fn simple(rom_path: &'a Path, prg_rom: Vec, chr_rom: Vec) -> MapperParams<'a> { 67 | MapperParams { 68 | prg_rom: prg_rom, 69 | chr_rom: chr_rom, 70 | 71 | prg_ram_size: 0x2000, 72 | 73 | rom_path: rom_path, 74 | 75 | has_battery_backed_ram: false, 76 | mirroring_mode: ScreenMode::OneScreenLow, 77 | } 78 | } 79 | } 80 | 81 | impl Mapper { 82 | pub fn new(id: u16, params: MapperParams) -> Box { 83 | match id { 84 | 0 => mapper000::new(params), 85 | 1 => mmc1::new(params), 86 | m => panic!("Unsupported Mapper: {}", m), 87 | } 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | pub fn create_test_mapper(prg_rom: Vec, chr_rom: Vec, mode: ScreenMode) -> Box { 93 | let path_buf = ::std::path::PathBuf::new(); 94 | let path = path_buf.as_path(); 95 | let mut params = MapperParams::simple(path, prg_rom, chr_rom); 96 | params.mirroring_mode = mode; 97 | Mapper::new(0, params) 98 | } 99 | -------------------------------------------------------------------------------- /src/apu/triangle.rs: -------------------------------------------------------------------------------- 1 | //! Contains structures used by the NES's triangle channel. 2 | 3 | use apu::Writable; 4 | use apu::buffer::Waveform; 5 | use apu::components::*; 6 | 7 | #[cfg_attr(rustfmt, rustfmt_skip)] 8 | static TRIANGLE_VOLUME: [i16; 32] = [ 9 | 0xF, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0, 10 | 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF]; 11 | 12 | struct LinearCounter { 13 | control: bool, 14 | reload: bool, 15 | value: u8, 16 | counter: u8, 17 | } 18 | 19 | impl LinearCounter { 20 | fn new() -> LinearCounter { 21 | LinearCounter { 22 | control: false, 23 | reload: false, 24 | value: 0, 25 | counter: 0, 26 | } 27 | } 28 | 29 | fn write(&mut self, val: u8) { 30 | self.value = val & 0b0111_1111; 31 | self.control = val & 0b1000_000 != 0; 32 | } 33 | 34 | fn tick(&mut self) { 35 | if self.reload { 36 | self.counter = self.value; 37 | } else { 38 | self.counter = self.counter.saturating_sub(1); 39 | } 40 | 41 | if !self.control { 42 | self.reload = false; 43 | } 44 | } 45 | 46 | fn audible(&self) -> bool { 47 | self.counter > 0 48 | } 49 | } 50 | 51 | pub struct Triangle { 52 | counter: LinearCounter, 53 | timer: Timer, 54 | pub length: Length, 55 | 56 | waveform: Waveform, 57 | volume_index: usize, 58 | } 59 | 60 | impl Triangle { 61 | pub fn new(waveform: Waveform) -> Triangle { 62 | Triangle { 63 | counter: LinearCounter::new(), 64 | timer: Timer::new(1), 65 | length: Length::new(7), 66 | 67 | waveform: waveform, 68 | volume_index: 0, 69 | } 70 | } 71 | 72 | pub fn length_tick(&mut self) { 73 | self.length.tick(); 74 | } 75 | 76 | pub fn envelope_tick(&mut self) { 77 | self.counter.tick(); 78 | } 79 | 80 | pub fn play(&mut self, from_cyc: u32, to_cyc: u32) { 81 | if !self.counter.audible() || !self.length.audible() { 82 | self.waveform.set_amplitude(0, from_cyc); 83 | return; 84 | } 85 | 86 | let mut current_cycle = from_cyc; 87 | while let TimerClock::Clock = self.timer.run(&mut current_cycle, to_cyc) { 88 | self.volume_index = (self.volume_index + 1) % 32; 89 | let volume = TRIANGLE_VOLUME[self.volume_index]; 90 | self.waveform.set_amplitude(volume, current_cycle); 91 | } 92 | } 93 | } 94 | 95 | impl Writable for Triangle { 96 | fn write(&mut self, idx: u16, val: u8) { 97 | match idx % 4 { 98 | 0 => { 99 | self.length.write_halt(val); 100 | self.counter.write(val); 101 | } 102 | 1 => (), 103 | 2 => self.timer.write_low(val), 104 | 3 => { 105 | self.length.write_counter(val); 106 | self.timer.write_high(val); 107 | self.counter.reload = true; 108 | } 109 | _ => (), 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/mappers/bank.rs: -------------------------------------------------------------------------------- 1 | use mappers::RomAddress; 2 | use std::ops::Range; 3 | 4 | pub struct RomBank { 5 | data: Box<[u8]>, 6 | } 7 | 8 | const BANK_SIZE: usize = 0x1000; 9 | 10 | impl RomBank { 11 | pub fn new(data: Vec) -> RomBank { 12 | if data.len() != BANK_SIZE { 13 | panic!("Unexpected bank size {}", data.len()); 14 | } 15 | 16 | RomBank { 17 | data: data.into_boxed_slice(), 18 | } 19 | } 20 | 21 | pub fn read(&self, idx: u16) -> u8 { 22 | unsafe { 23 | *self.data 24 | .get_unchecked((idx & (BANK_SIZE as u16 - 1)) as usize) 25 | } 26 | } 27 | 28 | pub fn write(&mut self, _: u16, _: u8) { 29 | // Do Nothing 30 | } 31 | } 32 | 33 | 34 | pub struct MappingTable { 35 | // All banks of ROM 36 | banks: Box<[RomBank]>, 37 | 38 | // Mappings from CPU addresses to bank indexes. 39 | // Indexed in terms of pages starting at 0x8000. 40 | mappings: [usize; 8], 41 | 42 | // Minimum window size in units of BANK_SIZE bytes 43 | min_window_size: usize, 44 | } 45 | 46 | fn to_page_num(addr: u16) -> usize { 47 | assert!(addr >= 0x8000); 48 | ((addr >> 12) & 0b0111) as usize 49 | } 50 | 51 | impl MappingTable { 52 | /// Create a MappingTable from the given PRM ROM data and minimum window 53 | /// size (in units of BANK_SIZE bytes) 54 | pub fn new(rom: Vec, min_window_size: usize) -> MappingTable { 55 | assert!(min_window_size <= 8); 56 | let mut banks: Vec = vec![]; 57 | let bank_count = rom.len() / BANK_SIZE; 58 | let mut remaining_rom = rom; 59 | for _ in 0..bank_count { 60 | let mut current_bank = remaining_rom; 61 | remaining_rom = current_bank.split_off(BANK_SIZE); 62 | banks.push(RomBank::new(current_bank)); 63 | } 64 | 65 | MappingTable { 66 | banks: banks.into_boxed_slice(), 67 | mappings: [0; 8], 68 | min_window_size: min_window_size, 69 | } 70 | } 71 | 72 | pub fn get_bank(&self, addr: u16) -> &RomBank { 73 | let index = self.mappings[to_page_num(addr)]; 74 | &self.banks[index] 75 | } 76 | 77 | pub fn get_bank_mut(&mut self, addr: u16) -> &mut RomBank { 78 | let index = self.mappings[to_page_num(addr)]; 79 | &mut self.banks[index] 80 | } 81 | 82 | pub fn get_rom_address(&self, addr: u16) -> RomAddress { 83 | let bank_id = self.mappings[to_page_num(addr)]; 84 | RomAddress { 85 | window_id: bank_id / self.min_window_size, 86 | offset: ((self.min_window_size * BANK_SIZE) as u16 - 1) & addr, 87 | } 88 | } 89 | 90 | pub fn bank_count(&self) -> usize { 91 | self.banks.len() 92 | } 93 | 94 | pub fn map_page(&mut self, page: usize, bank: usize) { 95 | self.mappings[page] = bank; 96 | } 97 | 98 | pub fn map_pages_linear(&mut self, range: Range, starting_bank: usize) { 99 | let mut cur_bank = starting_bank; 100 | for page in range { 101 | self.mappings[page] = cur_bank; 102 | cur_bank += 1; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/apu/noise.rs: -------------------------------------------------------------------------------- 1 | //! Contains structures used by the NES's noise channel. 2 | 3 | use apu::Writable; 4 | use apu::buffer::*; 5 | use apu::components::*; 6 | 7 | static PERIOD_TABLE: [u16; 16] = [ 8 | 0x0004, 9 | 0x0008, 10 | 0x0010, 11 | 0x0020, 12 | 0x0040, 13 | 0x0060, 14 | 0x0080, 15 | 0x00A0, 16 | 0x00CA, 17 | 0x00FE, 18 | 0x017C, 19 | 0x01FC, 20 | 0x02FA, 21 | 0x03F8, 22 | 0x07F2, 23 | 0x0FE4, 24 | ]; 25 | 26 | struct LinearFeedbackShiftRegister { 27 | value: u16, 28 | mode: u8, 29 | } 30 | 31 | impl LinearFeedbackShiftRegister { 32 | fn new() -> LinearFeedbackShiftRegister { 33 | LinearFeedbackShiftRegister { value: 1, mode: 0 } 34 | } 35 | 36 | fn shift(&mut self) -> bool { 37 | let bit0 = self.value & 0x01; 38 | let bit1 = self.other_bit(); 39 | 40 | let new_bit = bit0 ^ bit1; 41 | 42 | self.value = (self.value >> 1) | (new_bit << 14); 43 | self.value & 0x01 == 1 44 | } 45 | 46 | fn other_bit(&self) -> u16 { 47 | if self.mode == 0 { 48 | (self.value & (0x01 << 1)) >> 1 49 | } else { 50 | (self.value & (0x01 << 6)) >> 6 51 | } 52 | } 53 | 54 | fn set_mode(&mut self, mode: u8) { 55 | self.mode = mode; 56 | } 57 | } 58 | 59 | pub struct Noise { 60 | envelope: Envelope, 61 | pub length: Length, 62 | 63 | timer: Timer, 64 | shifter: LinearFeedbackShiftRegister, 65 | 66 | waveform: Waveform, 67 | } 68 | 69 | impl Noise { 70 | pub fn new(waveform: Waveform) -> Noise { 71 | Noise { 72 | envelope: Envelope::new(), 73 | length: Length::new(5), 74 | 75 | timer: Timer::new(1), 76 | 77 | waveform: waveform, 78 | 79 | shifter: LinearFeedbackShiftRegister::new(), 80 | } 81 | } 82 | 83 | pub fn length_tick(&mut self) { 84 | self.length.tick(); 85 | } 86 | 87 | pub fn envelope_tick(&mut self) { 88 | self.envelope.tick(); 89 | } 90 | 91 | pub fn play(&mut self, from_cyc: u32, to_cyc: u32) { 92 | if !self.length.audible() { 93 | self.waveform.set_amplitude(0, from_cyc); 94 | return; 95 | } 96 | 97 | let volume = self.envelope.volume(); 98 | 99 | let mut current_cyc = from_cyc; 100 | while let TimerClock::Clock = self.timer.run(&mut current_cyc, to_cyc) { 101 | let enabled = self.shifter.shift(); 102 | let amp = if enabled { volume } else { 0 }; 103 | self.waveform.set_amplitude(amp, current_cyc); 104 | } 105 | } 106 | } 107 | 108 | impl Writable for Noise { 109 | fn write(&mut self, idx: u16, val: u8) { 110 | match idx % 4 { 111 | 0 => { 112 | self.length.write_halt(val); 113 | self.envelope.write(val); 114 | } 115 | 2 => { 116 | let mode = (val & 0b1000_0000) >> 7; 117 | self.shifter.set_mode(mode); 118 | let period_index = val & 0b0000_1111; 119 | self.timer.set_period(PERIOD_TABLE[period_index as usize]); 120 | } 121 | 3 => self.length.write_counter(val), 122 | _ => (), 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/cart/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ines; 2 | 3 | 4 | use cart::ines::{Rom, RomError}; 5 | use mappers::{Mapper, MapperParams, RomAddress, RomBank}; 6 | use std::fs::File; 7 | use std::io; 8 | use std::io::prelude::*; 9 | use std::path::Path; 10 | 11 | #[derive(PartialEq, Debug, Clone, Copy)] 12 | pub enum ScreenMode { 13 | Horizontal, 14 | Vertical, 15 | FourScreen, 16 | OneScreenLow, 17 | OneScreenHigh, 18 | } 19 | 20 | #[derive(PartialEq, Debug, Clone, Copy)] 21 | pub enum System { 22 | NES, 23 | Vs, 24 | PC10, 25 | } 26 | 27 | #[derive(PartialEq, Debug, Clone, Copy)] 28 | pub enum TvFormat { 29 | NTSC, 30 | PAL, 31 | } 32 | 33 | pub struct Cart { 34 | mapper: Box, 35 | pub system: System, 36 | pub tv: TvFormat, 37 | } 38 | 39 | quick_error! { 40 | #[derive(Debug)] 41 | pub enum RomReadError { 42 | Io(err: io::Error) { 43 | display("IO Error: {}", err) 44 | description(err.description()) 45 | cause(err) 46 | from() 47 | } 48 | Parse(err: RomError) { 49 | display("ROM Error: {}", err) 50 | description(err.description()) 51 | cause(err) 52 | from() 53 | } 54 | } 55 | } 56 | 57 | impl Cart { 58 | pub fn prg_rom_read(&mut self, idx: u16) -> &RomBank { 59 | self.mapper.prg_rom_read(idx) 60 | } 61 | pub fn prg_rom_write(&mut self, idx: u16, val: u8) -> &mut RomBank { 62 | self.mapper.prg_rom_write(idx, val) 63 | } 64 | pub fn prg_rom_address(&self, idx: u16) -> RomAddress { 65 | self.mapper.prg_rom_address(idx) 66 | } 67 | pub fn prg_ram_read(&mut self, idx: u16) -> u8 { 68 | self.mapper.prg_ram_read(idx) 69 | } 70 | pub fn prg_ram_write(&mut self, idx: u16, val: u8) { 71 | self.mapper.prg_ram_write(idx, val) 72 | } 73 | pub fn chr_read(&mut self, idx: u16) -> u8 { 74 | self.mapper.chr_read(idx) 75 | } 76 | pub fn chr_write(&mut self, idx: u16, val: u8) { 77 | self.mapper.chr_write(idx, val) 78 | } 79 | 80 | pub fn new(mapper: Box) -> Cart { 81 | Cart { 82 | mapper: mapper, 83 | system: System::NES, 84 | tv: TvFormat::NTSC, 85 | } 86 | } 87 | 88 | pub fn get_mirroring_table(&self) -> &[u16; 4] { 89 | self.mapper.get_mirroring_table() 90 | } 91 | 92 | pub fn read(path: &Path) -> Result { 93 | let mut file = try!(File::open(path)); 94 | let mut buf = vec![]; 95 | try!(file.read_to_end(&mut buf)); 96 | let rom = try!(Rom::parse(&buf)); 97 | 98 | let mapper = rom.mapper(); 99 | let screen_mode = rom.screen_mode(); 100 | let system = rom.system(); 101 | let tv = rom.tv_system(); 102 | let sram = rom.sram(); 103 | let (prg_rom, chr_rom, prg_ram_size) = (rom.prg_rom, rom.chr_rom, rom.prg_ram_size); 104 | 105 | let params = MapperParams { 106 | prg_rom: prg_rom, 107 | chr_rom: chr_rom, 108 | 109 | prg_ram_size: prg_ram_size, 110 | 111 | rom_path: path, 112 | 113 | has_battery_backed_ram: sram, 114 | mirroring_mode: screen_mode, 115 | }; 116 | 117 | let mapper = Mapper::new(mapper as u16, params); 118 | Ok(Cart { 119 | mapper: mapper, 120 | system: system, 121 | tv: tv, 122 | }) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate corrosion; 2 | extern crate stopwatch; 3 | extern crate config; 4 | 5 | 6 | use config::{Config, File}; 7 | 8 | use corrosion::{Emulator, EmulatorBuilder, Settings}; 9 | use corrosion::cart::Cart; 10 | use corrosion::sdl2::EventPump; 11 | use corrosion::sdl2::event::Event; 12 | use std::cell::RefCell; 13 | use std::env; 14 | use std::path::Path; 15 | 16 | use std::rc::Rc; 17 | use stopwatch::Stopwatch; 18 | 19 | fn main() { 20 | let args = env::args(); 21 | let file_name = args.skip(1).next().expect("No ROM file provided."); 22 | let path = Path::new(&file_name); 23 | let cart = Cart::read(&path).expect("Failed to read ROM File"); 24 | let config = load_config(); 25 | start_emulator(cart, config); 26 | } 27 | 28 | fn load_config() -> Config { 29 | let mut s = Config::new(); 30 | s.merge(File::with_name("config/default").required(false)) 31 | .expect("Failed to read config file"); 32 | s 33 | } 34 | 35 | fn get_bool(config: &Config, key: &str, default: bool) -> bool { 36 | config.get_bool(key).unwrap_or(default) 37 | } 38 | 39 | fn make_emulator_settings(config: &Config) -> Settings { 40 | let defaults: Settings = Default::default(); 41 | Settings { 42 | jit: get_bool(&config, "jit", defaults.jit), 43 | graphics_enabled: get_bool(&config, "graphics_enabled", defaults.graphics_enabled), 44 | sound_enabled: get_bool(&config, "sound_enabled", defaults.sound_enabled), 45 | 46 | trace_cpu: get_bool(&config, "debug.trace_cpu", defaults.trace_cpu), 47 | disassemble_functions: get_bool(&config, "debug.disassemble_functions", defaults.disassemble_functions), 48 | } 49 | } 50 | 51 | #[cfg(feature = "debug_features")] 52 | fn mouse_pick(event_pump: &Rc>, emulator: &Emulator) { 53 | let mouse_state = event_pump.borrow().mouse_state(); 54 | // Should get this from the screen, but eh. 55 | let size_factor = 3; 56 | let (px_x, px_y) = (mouse_state.x() / size_factor, mouse_state.y() / size_factor); 57 | emulator.mouse_pick(px_x, px_y); 58 | } 59 | 60 | #[cfg(not(feature = "debug_features"))] 61 | fn mouse_pick(_: &Rc>, _: &Emulator) {} 62 | 63 | fn pump_events(pump: &Rc>) -> bool { 64 | for event in pump.borrow_mut().poll_iter() { 65 | if let Event::Quit { .. } = event { 66 | return true; 67 | } 68 | } 69 | false 70 | } 71 | 72 | fn get_movie_file() -> Option { 73 | std::env::args() 74 | .skip_while(|arg| arg != "--movie") 75 | .skip(1) 76 | .next() 77 | } 78 | 79 | fn start_emulator(cart: Cart, config: Config) { 80 | let sdl = corrosion::sdl2::init().unwrap(); 81 | let event_pump = Rc::new(RefCell::new(sdl.event_pump().unwrap())); 82 | 83 | let mut builder = 84 | EmulatorBuilder::new_sdl(cart, make_emulator_settings(&config), &sdl, &event_pump); 85 | 86 | if let Some(file) = get_movie_file() { 87 | let fm2io = corrosion::io::fm2::FM2IO::read(file).unwrap(); 88 | builder.io = Box::new(fm2io) 89 | } 90 | 91 | let mut emulator = builder.build(); 92 | 93 | let mut stopwatch = Stopwatch::start_new(); 94 | let smoothing = 0.9; 95 | let mut avg_frame_time = 0.0f64; 96 | let mousepick_enabled = config.get_bool("debug.mousepick").unwrap_or(false); 97 | loop { 98 | if pump_events(&event_pump) || emulator.halted() { 99 | break; 100 | } 101 | emulator.run_frame(); 102 | let current = stopwatch.elapsed().num_nanoseconds().unwrap() as f64; 103 | avg_frame_time = (avg_frame_time * smoothing) + (current * (1.0 - smoothing)); 104 | 105 | if mousepick_enabled { 106 | mouse_pick(&event_pump, &emulator); 107 | } 108 | 109 | // println!("Frames per second:{:.*}", 2, 1000000000.0 / avg_frame_time); 110 | stopwatch.restart(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/screen/sdl.rs: -------------------------------------------------------------------------------- 1 | use ppu::{Color, SCREEN_BUFFER_SIZE, SCREEN_HEIGHT, SCREEN_WIDTH}; 2 | use screen::Screen; 3 | use sdl2::{Sdl, VideoSubsystem}; 4 | use sdl2::pixels::PixelFormatEnum; 5 | use sdl2::rect::Rect; 6 | use sdl2::render::{Renderer, Texture}; 7 | 8 | #[allow(dead_code)] 9 | pub struct SDLScreen<'a> { 10 | video: VideoSubsystem, 11 | renderer: Renderer<'a>, 12 | texture: Texture, 13 | } 14 | 15 | const SCALE: usize = 3; 16 | 17 | impl<'a> SDLScreen<'a> { 18 | pub fn new(sdl_context: &Sdl) -> SDLScreen<'a> { 19 | let video_subsystem = sdl_context.video().unwrap(); 20 | 21 | let window = video_subsystem 22 | .window( 23 | "Corrosion", 24 | (SCREEN_WIDTH * SCALE) as u32, 25 | (SCREEN_HEIGHT * SCALE) as u32, 26 | ) 27 | .position_centered() 28 | .opengl() 29 | .build() 30 | .unwrap(); 31 | 32 | let mut renderer = window.renderer().present_vsync().build().unwrap(); 33 | renderer 34 | .set_logical_size(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32) 35 | .unwrap(); 36 | 37 | let texture = renderer 38 | .create_texture_streaming( 39 | PixelFormatEnum::RGB24, 40 | SCREEN_WIDTH as u32, 41 | SCREEN_HEIGHT as u32, 42 | ) 43 | .unwrap(); 44 | SDLScreen { 45 | video: video_subsystem, 46 | renderer: renderer, 47 | texture: texture, 48 | } 49 | } 50 | } 51 | 52 | // Using a hard-coded palette for now. Will add .pal file support later, 53 | // maybe proper NTSC video decoding eventually. 54 | #[cfg_attr(rustfmt, rustfmt_skip)] 55 | static PALETTE: [u8; 192] = [ 56 | 84, 84, 84, 0, 30, 116, 8, 16, 144, 48, 0, 136, 68, 0, 100, 92, 0, 48, 84, 4, 0, 60, 24, 0, 32, 42, 0, 8, 58, 0, 0, 64, 0, 0, 60, 0, 0, 50, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 57 | 152, 150, 152, 8, 76, 196, 48, 50, 236, 92, 30, 228, 136, 20, 176, 160, 20, 100, 152, 34, 32, 120, 60, 0, 84, 90, 0, 40, 114, 0, 8, 124, 0, 0, 118, 40, 0, 102, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58 | 236, 238, 236, 76, 154, 236, 120, 124, 236, 176, 98, 236, 228, 84, 236, 236, 88, 180, 236, 106, 100, 212, 136, 32, 160, 170, 0, 116, 196, 0, 76, 208, 32, 56, 204, 108, 56, 180, 204, 60, 60, 60, 0, 0, 0, 0, 0, 0, 59 | 236, 238, 236, 168, 204, 236, 188, 188, 236, 212, 178, 236, 236, 174, 236, 236, 174, 212, 236, 180, 176, 228, 196, 144, 204, 210, 120, 180, 222, 120, 168, 226, 144, 152, 226, 180, 160, 214, 228, 160, 162, 160, 0, 0, 0, 0, 0, 0, 60 | ]; 61 | 62 | fn copy_to_texture(buf: &[Color; SCREEN_BUFFER_SIZE], buffer: &mut [u8], pitch: usize) { 63 | for y in 0..SCREEN_HEIGHT { 64 | for x in 0..SCREEN_WIDTH { 65 | let nes_idx = y * SCREEN_WIDTH + x; 66 | let color = buf[nes_idx]; 67 | let pal_idx = color.bits() as usize * 3; 68 | let offset = y * pitch + x * 3; 69 | buffer[offset] = PALETTE[pal_idx]; 70 | buffer[offset + 1] = PALETTE[pal_idx + 1]; 71 | buffer[offset + 2] = PALETTE[pal_idx + 2]; 72 | } 73 | } 74 | } 75 | 76 | impl<'a> Screen for SDLScreen<'a> { 77 | fn draw(&mut self, buf: &[Color; SCREEN_BUFFER_SIZE]) { 78 | self.texture 79 | .with_lock(None, |buffer: &mut [u8], pitch: usize| { 80 | copy_to_texture(buf, buffer, pitch); 81 | }) 82 | .unwrap(); 83 | 84 | self.renderer 85 | .copy( 86 | &self.texture, 87 | None, 88 | Some(Rect::new(0, 0, SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32)), 89 | ) 90 | .unwrap(); 91 | self.renderer.present(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | #![feature(plugin)] 3 | #![feature(asm)] 4 | #![feature(naked_functions)] 5 | 6 | #![plugin(dynasm)] 7 | 8 | #![allow(unused_features)] 9 | #![allow(unknown_lints)] 10 | #![allow(new_without_default)] 11 | #![allow(match_same_arms)] 12 | 13 | #[cfg(target_arch = "x86_64")] 14 | extern crate dynasmrt; 15 | 16 | #[macro_use] 17 | extern crate bitflags; 18 | 19 | #[macro_use] 20 | extern crate quick_error; 21 | 22 | #[macro_use] 23 | extern crate nom; 24 | 25 | pub extern crate sdl2; 26 | extern crate blip_buf; 27 | extern crate memmap; 28 | extern crate fnv; 29 | 30 | #[cfg(feature = "vectorize")] 31 | extern crate simd; 32 | 33 | pub mod cart; 34 | pub mod memory; 35 | pub mod mappers; 36 | pub mod ppu; 37 | pub mod apu; 38 | pub mod io; 39 | pub mod cpu; 40 | pub mod screen; 41 | pub mod audio; 42 | 43 | mod util; 44 | 45 | #[cfg(test)] 46 | mod tests; 47 | 48 | use apu::APU; 49 | use cart::Cart; 50 | use cpu::CPU; 51 | use io::IO; 52 | use ppu::PPU; 53 | use std::cell::RefCell; 54 | use std::cell::UnsafeCell; 55 | 56 | use std::rc::Rc; 57 | 58 | #[derive(Debug, Clone)] 59 | pub struct Settings { 60 | pub jit: bool, 61 | pub graphics_enabled: bool, 62 | pub sound_enabled: bool, 63 | 64 | // The following will only be used if compiled with the debug_features feature 65 | pub trace_cpu: bool, 66 | pub disassemble_functions: bool, 67 | } 68 | 69 | impl Default for Settings { 70 | fn default() -> Settings { 71 | Settings { 72 | jit: false, 73 | graphics_enabled: true, 74 | sound_enabled: true, 75 | 76 | trace_cpu: false, 77 | disassemble_functions: false, 78 | } 79 | } 80 | } 81 | 82 | pub struct EmulatorBuilder { 83 | cart: Cart, 84 | settings: Settings, 85 | 86 | pub screen: Box, 87 | pub audio_out: Box, 88 | pub io: Box, 89 | } 90 | 91 | impl EmulatorBuilder { 92 | pub fn new(cart: Cart, settings: Settings) -> EmulatorBuilder { 93 | EmulatorBuilder { 94 | cart: cart, 95 | settings: settings, 96 | 97 | screen: Box::new(screen::DummyScreen::default()), 98 | audio_out: Box::new(audio::DummyAudioOut), 99 | io: Box::new(io::DummyIO::Dummy), 100 | } 101 | } 102 | 103 | pub fn new_sdl( 104 | cart: Cart, 105 | settings: Settings, 106 | sdl: &sdl2::Sdl, 107 | event_pump: &Rc>, 108 | ) -> EmulatorBuilder { 109 | let sound_enabled = settings.sound_enabled; 110 | let mut builder = EmulatorBuilder::new(cart, settings); 111 | 112 | builder.screen = Box::new(screen::sdl::SDLScreen::new(sdl)); 113 | if sound_enabled { 114 | builder.audio_out = Box::new(audio::sdl::SDLAudioOut::new(sdl)); 115 | } 116 | builder.io = Box::new(io::sdl::SdlIO::new(event_pump.clone())); 117 | 118 | builder 119 | } 120 | 121 | pub fn build(self) -> Emulator { 122 | let settings = Rc::new(self.settings); 123 | let dispatcher = cpu::dispatcher::Dispatcher::new(); 124 | let cart: Rc> = Rc::new(UnsafeCell::new(self.cart)); 125 | let ppu = PPU::new(settings.clone(), cart.clone(), self.screen); 126 | let apu = APU::new(settings.clone(), self.audio_out); 127 | let mut cpu = CPU::new(settings, ppu, apu, self.io, cart, dispatcher); 128 | cpu.init(); 129 | 130 | Emulator { cpu: cpu } 131 | } 132 | } 133 | 134 | pub struct Emulator { 135 | cpu: CPU, 136 | } 137 | 138 | impl Emulator { 139 | pub fn run_frame(&mut self) { 140 | self.cpu.run_frame(); 141 | } 142 | 143 | pub fn halted(&self) -> bool { 144 | self.cpu.halted() 145 | } 146 | 147 | #[cfg(feature = "debug_features")] 148 | pub fn mouse_pick(&self, px_x: i32, px_y: i32) { 149 | self.cpu.ppu.mouse_pick(px_x, px_y); 150 | } 151 | 152 | pub fn rendering_enabled(&self) -> bool { 153 | self.cpu.ppu.rendering_enabled() 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/cpu/dispatcher.rs: -------------------------------------------------------------------------------- 1 | use cpu::CPU; 2 | 3 | #[cfg(target_arch = "x86_64")] 4 | use cpu::compiler; 5 | 6 | #[cfg(target_arch = "x86_64")] 7 | use cpu::compiler::ExecutableBlock; 8 | use fnv::{FnvHashMap, FnvHashSet}; 9 | 10 | #[cfg(target_arch = "x86_64")] 11 | use mappers::RomAddress; 12 | 13 | #[cfg(not(target_arch = "x86_64"))] 14 | pub struct Dispatcher {} 15 | #[cfg(not(target_arch = "x86_64"))] 16 | impl Dispatcher { 17 | pub fn new() -> Dispatcher { 18 | Dispatcher {} 19 | } 20 | 21 | pub fn jump(&mut self, _: &mut CPU) {} 22 | } 23 | 24 | #[cfg(target_arch = "x86_64")] 25 | pub struct Dispatcher { 26 | table: FnvHashMap, 27 | compiling: FnvHashSet, 28 | } 29 | #[cfg(target_arch = "x86_64")] 30 | struct Block { 31 | code: ExecutableBlock, 32 | locked: bool, 33 | } 34 | 35 | #[cfg(feature = "debug_features")] 36 | fn disasm_function(cpu: &mut CPU, addr: u16) { 37 | ::cpu::disasm::Disassembler::new(cpu).disasm_function(addr); 38 | } 39 | 40 | #[cfg(not(feature = "debug_features"))] 41 | fn disasm_function(_: &mut CPU, _: u16) {} 42 | 43 | impl Default for Dispatcher { 44 | fn default() -> Dispatcher { 45 | Dispatcher::new() 46 | } 47 | } 48 | 49 | #[cfg(target_arch = "x86_64")] 50 | impl Dispatcher { 51 | pub fn new() -> Dispatcher { 52 | Dispatcher { 53 | table: FnvHashMap::default(), 54 | compiling: FnvHashSet::default(), 55 | } 56 | } 57 | 58 | fn get_rom_addr(&self, addr: u16, cpu: &CPU) -> RomAddress { 59 | unsafe { (*cpu.cart.get()).prg_rom_address(addr) } 60 | } 61 | 62 | pub fn jump(&mut self, cpu: &mut CPU) { 63 | let addr = cpu.regs.pc; 64 | let executable = &self.get_block(addr, cpu).code; 65 | executable.call(cpu); 66 | } 67 | 68 | pub fn lock_block( 69 | &mut self, 70 | target_addr: u16, 71 | caller_addr: u16, 72 | cpu: &mut CPU, 73 | ) -> Option<&ExecutableBlock> { 74 | if target_addr < 0x8000 { 75 | return None; 76 | } 77 | 78 | let target_rom_addr = self.get_rom_addr(target_addr, cpu); 79 | if self.compiling.contains(&target_rom_addr) { 80 | // Prevent infinite recursion. 81 | None 82 | } else if target_rom_addr.window_id == self.get_rom_addr(caller_addr, cpu).window_id { 83 | if self.should_compile(&target_rom_addr) { 84 | self.compile(target_addr, &target_rom_addr, cpu); 85 | } 86 | self.table.get_mut(&target_rom_addr).unwrap().locked = true; 87 | Some(&self.table.get(&target_rom_addr).unwrap().code) 88 | } else { 89 | None 90 | } 91 | } 92 | 93 | fn get_block(&mut self, addr: u16, cpu: &mut CPU) -> &Block { 94 | let rom_addr = self.get_rom_addr(addr, cpu); 95 | if self.should_compile(&rom_addr) { 96 | self.compile(addr, &rom_addr, cpu); 97 | } 98 | self.table.get(&rom_addr).unwrap() 99 | } 100 | 101 | fn should_compile(&self, addr: &RomAddress) -> bool { 102 | !self.table.contains_key(addr) 103 | } 104 | 105 | fn compile(&mut self, addr: u16, rom_addr: &RomAddress, cpu: &mut CPU) { 106 | if cpu.settings.disassemble_functions { 107 | disasm_function(cpu, addr); 108 | } 109 | 110 | self.compiling.insert(rom_addr.clone()); 111 | 112 | let executables = compiler::compile(addr, cpu, self); 113 | for (addr, block) in executables { 114 | let rom_addr = self.get_rom_addr(addr, cpu); 115 | 116 | // Don't overwrite (and therefore drop) locked blocks, they're linked to other 117 | // blocks. 118 | // TODO: Track those links and patch them to the new address 119 | if let Some(block) = self.table.get(&rom_addr) { 120 | if block.locked { 121 | continue; 122 | } 123 | } 124 | 125 | self.table.insert( 126 | rom_addr, 127 | Block { 128 | code: block, 129 | locked: false, 130 | }, 131 | ); 132 | } 133 | 134 | self.compiling.remove(rom_addr); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/apu/square.rs: -------------------------------------------------------------------------------- 1 | //! Contains structures used only by the NES's two square-wave channels. 2 | 3 | use apu::Writable; 4 | use apu::buffer::*; 5 | use apu::components::*; 6 | 7 | static SQUARE_DUTY_CYCLES: [[i16; 8]; 4] = [ 8 | [0, 1, -1, 0, 0, 0, 0, 0], 9 | [0, 1, 0, -1, 0, 0, 0, 0], 10 | [0, 1, 0, 0, 0, -1, 0, 0], 11 | [0, -1, 0, 1, 0, 0, 0, 0], 12 | ]; 13 | 14 | /// Represents the frequency-sweep units used by the two square channels. 15 | struct Sweep { 16 | enable: bool, 17 | period: u8, 18 | negate: bool, 19 | shift: u8, 20 | 21 | is_square2: bool, 22 | divider: u8, 23 | reload: bool, 24 | } 25 | 26 | impl Sweep { 27 | fn new(is_square2: bool) -> Sweep { 28 | Sweep { 29 | enable: false, 30 | period: 0, 31 | negate: false, 32 | shift: 0, 33 | 34 | is_square2: is_square2, 35 | divider: 0, 36 | reload: false, 37 | } 38 | } 39 | 40 | fn write(&mut self, val: u8) { 41 | self.enable = (val & 0b1000_0000) != 0; 42 | self.period = (val & 0b0111_0000) >> 4; 43 | self.negate = (val & 0b0000_1000) != 0; 44 | self.shift = val & 0b0000_0111; 45 | self.reload = true; 46 | } 47 | 48 | fn tick(&mut self, timer: &mut Timer) { 49 | if !self.enable { 50 | return; 51 | } 52 | 53 | self.divider = self.divider.saturating_sub(1); 54 | if self.divider == 0 { 55 | self.divider = self.period; 56 | let period_shift = self.period_shift(timer); 57 | timer.add_period_shift(period_shift); 58 | } 59 | 60 | if self.reload { 61 | self.divider = self.period; 62 | self.reload = false; 63 | } 64 | } 65 | 66 | fn audible(&self) -> bool { 67 | true 68 | } 69 | 70 | fn period_shift(&self, timer: &Timer) -> i16 { 71 | let mut shift = timer.period() as i16; 72 | shift >>= self.shift; 73 | if self.negate { 74 | shift = -shift; 75 | if self.is_square2 { 76 | shift += 1; 77 | } 78 | } 79 | shift 80 | } 81 | } 82 | pub struct Square { 83 | duty: usize, 84 | duty_index: usize, 85 | 86 | envelope: Envelope, 87 | sweep: Sweep, 88 | timer: Timer, 89 | pub length: Length, 90 | 91 | waveform: Waveform, 92 | } 93 | 94 | impl Square { 95 | pub fn new(is_square2: bool, waveform: Waveform) -> Square { 96 | Square { 97 | duty: 0, 98 | duty_index: 0, 99 | 100 | envelope: Envelope::new(), 101 | sweep: Sweep::new(is_square2), 102 | timer: Timer::new(2), 103 | length: Length::new(5), 104 | 105 | waveform: waveform, 106 | } 107 | } 108 | 109 | pub fn length_tick(&mut self) { 110 | self.length.tick(); 111 | let timer = &mut self.timer; 112 | self.sweep.tick(timer) 113 | } 114 | 115 | pub fn envelope_tick(&mut self) { 116 | self.envelope.tick(); 117 | } 118 | 119 | pub fn play(&mut self, from_cyc: u32, to_cyc: u32) { 120 | if !self.sweep.audible() || !self.length.audible() { 121 | self.waveform.set_amplitude(0, from_cyc); 122 | return; 123 | } 124 | 125 | let volume = self.envelope.volume(); 126 | 127 | let mut current_cyc = from_cyc; 128 | while let TimerClock::Clock = self.timer.run(&mut current_cyc, to_cyc) { 129 | self.duty_index = (self.duty_index + 1) % 8; 130 | match SQUARE_DUTY_CYCLES[self.duty][self.duty_index] { 131 | -1 => self.waveform.set_amplitude(0, current_cyc), 132 | 0 => (), 133 | 1 => self.waveform.set_amplitude(volume, current_cyc), 134 | _ => (), 135 | }; 136 | } 137 | } 138 | } 139 | 140 | impl Writable for Square { 141 | fn write(&mut self, idx: u16, val: u8) { 142 | match idx % 4 { 143 | 0 => { 144 | self.duty = (val >> 6) as usize; 145 | self.length.write_halt(val); 146 | self.envelope.write(val); 147 | } 148 | 1 => self.sweep.write(val), 149 | 2 => self.timer.write_low(val), 150 | 3 => { 151 | self.length.write_counter(val); 152 | self.timer.write_high(val); 153 | } 154 | _ => (), 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/audio/sdl.rs: -------------------------------------------------------------------------------- 1 | use super::AudioOut; 2 | use apu::Sample; 3 | use sdl2::AudioSubsystem; 4 | use sdl2::Sdl; 5 | use sdl2::audio::{AudioCallback, AudioDevice, AudioSpecDesired}; 6 | use std::cmp; 7 | use std::sync::{Condvar, Mutex}; 8 | use std::sync::Arc; 9 | 10 | const OUT_SAMPLE_RATE: i32 = 44100; 11 | const BUFFER_SIZE: usize = OUT_SAMPLE_RATE as usize / 15; 12 | 13 | struct BufferOut { 14 | samples: [Sample; BUFFER_SIZE], 15 | input_counter: usize, 16 | playback_counter: usize, 17 | input_samples: usize, 18 | too_slow: bool, 19 | condvar: Arc, 20 | } 21 | 22 | impl AudioCallback for BufferOut { 23 | type Channel = Sample; 24 | 25 | fn callback(&mut self, out: &mut [Sample]) { 26 | { 27 | let out_iter = out.iter_mut(); 28 | let in_iter = self.samples 29 | .iter() 30 | .cycle() 31 | .skip(self.playback_counter) 32 | .take(self.input_samples); 33 | 34 | for (dest, src) in out_iter.zip(in_iter) { 35 | *dest = *src; 36 | } 37 | } 38 | 39 | let transferred = cmp::min(out.len(), self.input_samples); 40 | self.input_samples -= transferred; 41 | self.playback_counter = (self.playback_counter + transferred) % self.samples.len(); 42 | 43 | { 44 | let out_iter = out.iter_mut().skip(transferred); 45 | // This should rarely, if ever, execute. 46 | for dest in out_iter { 47 | self.too_slow = true; 48 | *dest = 0; 49 | } 50 | if self.too_slow { 51 | self.input_counter = self.playback_counter; 52 | } 53 | } 54 | 55 | self.condvar.notify_one(); 56 | } 57 | } 58 | 59 | #[allow(dead_code)] 60 | pub struct SDLAudioOut { 61 | system: AudioSubsystem, 62 | device: AudioDevice, 63 | mutex: Mutex<()>, 64 | condvar: Arc, 65 | } 66 | 67 | impl AudioOut for SDLAudioOut { 68 | fn play(&mut self, buffer: &[Sample]) { 69 | self.wait(buffer.len()); 70 | let mut out = self.device.lock(); 71 | 72 | if out.too_slow { 73 | println!("Audio transfer can't keep up"); 74 | out.too_slow = false; 75 | } 76 | 77 | let mut in_index = 0; 78 | let mut out_index = out.input_counter; 79 | let out_len = out.samples.len(); 80 | let in_len = buffer.len(); 81 | 82 | while in_index < in_len { 83 | out.samples[out_index] = buffer[in_index]; 84 | in_index += 1; 85 | out_index += 1; 86 | if out_index == out_len { 87 | out_index = 0; 88 | } 89 | } 90 | out.input_counter = (out.input_counter + in_len) % out_len; 91 | out.input_samples += in_len; 92 | } 93 | 94 | fn sample_rate(&self) -> f64 { 95 | OUT_SAMPLE_RATE as f64 96 | } 97 | } 98 | 99 | impl SDLAudioOut { 100 | pub fn new(sdl: &Sdl) -> SDLAudioOut { 101 | let mutex = Mutex::new(()); 102 | let condvar = Condvar::new(); 103 | let condvar = Arc::new(condvar); 104 | 105 | let audio_subsystem = sdl.audio().unwrap(); 106 | 107 | let desired_spec = AudioSpecDesired { 108 | freq: Some(OUT_SAMPLE_RATE), 109 | channels: Some(1), 110 | samples: None, 111 | }; 112 | 113 | let device = audio_subsystem 114 | .open_playback(None, &desired_spec, |_| { 115 | BufferOut { 116 | samples: [0; BUFFER_SIZE], 117 | input_counter: 0, 118 | playback_counter: 0, 119 | input_samples: 0, 120 | too_slow: false, 121 | condvar: condvar.clone(), 122 | } 123 | }) 124 | .unwrap(); 125 | 126 | // Start playback 127 | device.resume(); 128 | 129 | SDLAudioOut { 130 | system: audio_subsystem, 131 | device: device, 132 | mutex: mutex, 133 | condvar: condvar, 134 | } 135 | } 136 | 137 | fn wait(&mut self, in_size: usize) { 138 | { 139 | let callback = self.device.lock(); 140 | if callback.input_samples + in_size <= callback.samples.len() { 141 | return; 142 | } 143 | } 144 | 145 | // If there isn't enough room for the transfer, wait until the callback is 146 | // called once, then check again. 147 | loop { 148 | let lock = self.mutex.lock().unwrap(); 149 | let _lock = self.condvar.wait(lock).unwrap(); 150 | let callback = self.device.lock(); 151 | if callback.input_samples + in_size <= callback.samples.len() { 152 | return; 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/apu/components.rs: -------------------------------------------------------------------------------- 1 | //! This module contains implementations of the common components used by the 2 | //! various NES sound channels. 3 | 4 | #[cfg_attr(rustfmt, rustfmt_skip)] 5 | static LENGTH_TABLE: [u8; 32] = [ 6 | 0x0A, 0xFE, 7 | 0x14, 0x02, 8 | 0x28, 0x04, 9 | 0x50, 0x06, 10 | 0xA0, 0x08, 11 | 0x3C, 0x0A, 12 | 0x0E, 0x0C, 13 | 0x1A, 0x0E, 14 | 0x0C, 0x10, 15 | 0x18, 0x12, 16 | 0x30, 0x14, 17 | 0x60, 0x16, 18 | 0xC0, 0x18, 19 | 0x48, 0x1A, 20 | 0x10, 0x1C, 21 | 0x20, 0x1E, 22 | ]; 23 | 24 | /// Represents the Length counter used by all NES sound channels except the DMC. 25 | #[derive(Debug)] 26 | pub struct Length { 27 | halt_bit: usize, 28 | halted: bool, 29 | enabled: bool, 30 | remaining: u8, 31 | } 32 | 33 | impl Length { 34 | pub fn write_halt(&mut self, val: u8) { 35 | self.halted = (val >> self.halt_bit) & 0x01 != 0; 36 | } 37 | 38 | pub fn write_counter(&mut self, val: u8) { 39 | if self.enabled { 40 | self.remaining = LENGTH_TABLE[(val >> 3) as usize]; 41 | } 42 | } 43 | 44 | pub fn tick(&mut self) { 45 | if !self.halted { 46 | self.remaining = self.remaining.saturating_sub(1); 47 | } 48 | } 49 | 50 | pub fn audible(&self) -> bool { 51 | self.remaining > 0 52 | } 53 | 54 | pub fn active(&self) -> u8 { 55 | if self.audible() { 1 } else { 0 } 56 | } 57 | 58 | pub fn set_enable(&mut self, enable: bool) { 59 | self.enabled = enable; 60 | if !enable { 61 | self.remaining = 0; 62 | } 63 | } 64 | 65 | pub fn new(halt_bit: usize) -> Length { 66 | Length { 67 | halt_bit: halt_bit, 68 | halted: false, 69 | enabled: false, 70 | remaining: 0, 71 | } 72 | } 73 | } 74 | 75 | /// Represents the Envelope Generator (volume setting) used by the pulse & 76 | /// noise channels. 77 | #[derive(Debug)] 78 | pub struct Envelope { 79 | should_loop: bool, 80 | constant_volume: bool, 81 | n: u8, 82 | 83 | divider: u8, 84 | counter: u8, 85 | } 86 | 87 | impl Envelope { 88 | pub fn new() -> Envelope { 89 | Envelope { 90 | should_loop: false, 91 | constant_volume: false, 92 | n: 0, 93 | divider: 0, 94 | counter: 0, 95 | } 96 | } 97 | 98 | pub fn write(&mut self, val: u8) { 99 | self.should_loop = (val >> 5) & 0x01 != 0; 100 | self.constant_volume = (val >> 4) & 0x01 != 0; 101 | self.n = val & 0x0F; 102 | self.divider = self.n; 103 | self.counter = 15; 104 | } 105 | 106 | pub fn tick(&mut self) { 107 | if self.divider == 0 { 108 | self.envelope_tick(); 109 | self.divider = self.n; 110 | } else { 111 | self.divider -= 1; 112 | } 113 | } 114 | 115 | pub fn envelope_tick(&mut self) { 116 | if self.should_loop && self.counter == 0 { 117 | self.counter = 15; 118 | } else { 119 | self.counter = self.counter.saturating_sub(1); 120 | } 121 | } 122 | 123 | pub fn volume(&self) -> i16 { 124 | if self.constant_volume { 125 | self.n as i16 126 | } else { 127 | self.counter as i16 128 | } 129 | } 130 | } 131 | 132 | #[derive(Debug)] 133 | pub enum TimerClock { 134 | Clock, 135 | NoClock, 136 | } 137 | 138 | /// Represents the CPU-clock timers used by all of the NES channels. 139 | #[derive(Debug)] 140 | pub struct Timer { 141 | period: u16, 142 | divider: u32, 143 | remaining: u32, 144 | } 145 | 146 | impl Timer { 147 | pub fn new(divider: u32) -> Timer { 148 | Timer { 149 | period: 0, 150 | divider: divider, 151 | remaining: 0, 152 | } 153 | } 154 | 155 | pub fn write_low(&mut self, val: u8) { 156 | self.period = (self.period & 0xFF00) | val as u16; 157 | } 158 | 159 | pub fn write_high(&mut self, val: u8) { 160 | self.period = (self.period & 0x00FF) | (val as u16 & 0x0007) << 8; 161 | } 162 | 163 | pub fn set_period(&mut self, period: u16) { 164 | self.period = period; 165 | } 166 | 167 | pub fn add_period_shift(&mut self, shift: i16) { 168 | let new_period = (self.period as i16).wrapping_add(shift); 169 | self.period = new_period as u16; 170 | } 171 | 172 | pub fn period(&self) -> u16 { 173 | self.period 174 | } 175 | 176 | fn wavelen(&self) -> u32 { 177 | (self.period as u32 + 1) * self.divider 178 | } 179 | 180 | /// Run the timer until the next clock, or until current_cyc reaches 181 | /// to_cycle. Returns either Clock or NoClock depending on if it reached a 182 | /// clock or not. 183 | pub fn run(&mut self, current_cyc: &mut u32, to_cyc: u32) -> TimerClock { 184 | let end_wavelen = *current_cyc + self.remaining; 185 | 186 | if end_wavelen < to_cyc { 187 | *current_cyc += self.remaining; 188 | self.remaining = self.wavelen(); 189 | TimerClock::Clock 190 | } else { 191 | self.remaining -= to_cyc - *current_cyc; 192 | *current_cyc = to_cyc; 193 | TimerClock::NoClock 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/mappers/mapper000.rs: -------------------------------------------------------------------------------- 1 | use super::{Mapper, MapperParams, RomAddress}; 2 | use super::bank::*; 3 | 4 | struct Mapper000 { 5 | prg_rom: MappingTable, 6 | chr_rom: Box<[u8]>, 7 | chr_ram: Box<[u8]>, 8 | prg_ram: Box<[u8]>, 9 | 10 | mode: &'static [u16; 4], 11 | } 12 | 13 | pub fn new(params: MapperParams) -> Box { 14 | let chr_ram = if params.chr_rom.is_empty() { 15 | vec![0u8; 0x2000].into_boxed_slice() 16 | } else { 17 | vec![0u8; 0].into_boxed_slice() 18 | }; 19 | 20 | let mut prg_rom_table = MappingTable::new(params.prg_rom, 8); 21 | let bank_count = prg_rom_table.bank_count(); 22 | for page in 0..8 { 23 | prg_rom_table.map_page(page, page % bank_count); 24 | } 25 | 26 | Box::new(Mapper000 { 27 | prg_rom: prg_rom_table, 28 | chr_rom: params.chr_rom.into_boxed_slice(), 29 | chr_ram: chr_ram, 30 | prg_ram: vec![0u8; params.prg_ram_size].into_boxed_slice(), 31 | mode: super::standard_mapping_tables(params.mirroring_mode), 32 | }) 33 | } 34 | 35 | impl Mapper for Mapper000 { 36 | fn prg_rom_read(&mut self, idx: u16) -> &RomBank { 37 | self.prg_rom.get_bank(idx) 38 | } 39 | 40 | fn prg_rom_write(&mut self, idx: u16, _: u8) -> &mut RomBank { 41 | self.prg_rom.get_bank_mut(idx) 42 | } 43 | 44 | fn prg_rom_address(&self, idx: u16) -> RomAddress { 45 | self.prg_rom.get_rom_address(idx) 46 | } 47 | 48 | fn prg_ram_read(&mut self, idx: u16) -> u8 { 49 | self.prg_ram[((idx - 0x6000) as usize % self.prg_ram.len())] 50 | } 51 | 52 | fn prg_ram_write(&mut self, idx: u16, val: u8) { 53 | let idx = (idx - 0x6000) as usize % self.prg_ram.len(); 54 | self.prg_ram[idx] = val; 55 | } 56 | 57 | fn chr_read(&mut self, idx: u16) -> u8 { 58 | if self.chr_rom.len() == 0 { 59 | self.chr_ram[idx as usize % self.chr_ram.len()] 60 | } else { 61 | self.chr_rom[idx as usize % self.chr_rom.len()] 62 | } 63 | } 64 | 65 | fn chr_write(&mut self, idx: u16, val: u8) { 66 | if self.chr_rom.len() == 0 { 67 | let len = self.chr_ram.len(); 68 | self.chr_ram[idx as usize % len] = val; 69 | } 70 | } 71 | 72 | fn get_mirroring_table(&self) -> &[u16; 4] { 73 | self.mode 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | use mappers::{Mapper, MapperParams}; 81 | 82 | #[test] 83 | fn test_can_create_mapper_0() { 84 | let path_buf = ::std::path::PathBuf::new(); 85 | let path = path_buf.as_path(); 86 | new(MapperParams::simple(path, vec![0u8; 0x1000], vec![])); 87 | } 88 | 89 | fn create_test_mapper() -> Box { 90 | let path_buf = ::std::path::PathBuf::new(); 91 | let path = path_buf.as_path(); 92 | new(MapperParams::simple( 93 | path, 94 | vec![0u8; 0x4000], 95 | vec![0u8; 0x4000], 96 | )) 97 | } 98 | 99 | #[test] 100 | fn test_prg_ram_read_write() { 101 | let path_buf = ::std::path::PathBuf::new(); 102 | let path = path_buf.as_path(); 103 | let mut params = MapperParams::simple(path, vec![0u8; 0x4000], vec![0u8; 0x4000]); 104 | params.prg_ram_size = 0x1000; 105 | let mut nrom = new(params); 106 | nrom.prg_ram_write(0x6111, 15); 107 | assert_eq!(nrom.prg_ram_read(0x6111), 15); 108 | 109 | nrom.prg_ram_write(0x6112, 16); 110 | assert_eq!(nrom.prg_ram_read(0x7112), 16); 111 | } 112 | 113 | #[test] 114 | fn test_prg_rom_read() { 115 | let path_buf = ::std::path::PathBuf::new(); 116 | let path = path_buf.as_path(); 117 | let prg_rom: Vec<_> = (0..0x4000).map(|val| (val % 0xFF) as u8).collect(); 118 | let mut mapper = new(MapperParams::simple(path, prg_rom, vec![0u8; 0x4000])); 119 | 120 | assert_eq!( 121 | mapper.prg_rom_read(0x8111).read(0x8111), 122 | mapper.prg_rom_read(0xC111).read(0xC111) 123 | ); 124 | } 125 | 126 | #[test] 127 | fn test_prg_rom_mirroring() { 128 | let path_buf = ::std::path::PathBuf::new(); 129 | let path = path_buf.as_path(); 130 | let mut prg_rom: Vec<_> = vec![0u8; 0x4000]; 131 | prg_rom[0x2612] = 0x15; 132 | let mut mapper = new(MapperParams::simple(path, prg_rom, vec![0u8; 0x1000])); 133 | assert_eq!(mapper.prg_rom_read(0xA612).read(0xA612), 0x15); 134 | } 135 | 136 | #[test] 137 | fn test_prg_rom_write() { 138 | let mut mapper = create_test_mapper(); 139 | 140 | mapper.prg_rom_write(0x8612, 15).write(0x8612, 15); 141 | assert_eq!(mapper.prg_rom_read(0x8612).read(0x8612), 0); 142 | } 143 | 144 | #[test] 145 | fn test_chr_rom_read() { 146 | let path_buf = ::std::path::PathBuf::new(); 147 | let path = path_buf.as_path(); 148 | let chr_rom: Vec<_> = (0..0x2000).map(|val| (val % 0xFF) as u8).collect(); 149 | let mut mapper = new(MapperParams::simple(path, vec![0u8; 0x4000], chr_rom)); 150 | 151 | assert_eq!(mapper.chr_read(0x8111), mapper.chr_read(0xC111)); 152 | } 153 | 154 | #[test] 155 | fn test_chr_rom_write() { 156 | let mut mapper = create_test_mapper(); 157 | 158 | mapper.chr_write(0x1612, 15); 159 | assert_eq!(mapper.chr_read(0x1612), 0); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/mappers/mmc1.rs: -------------------------------------------------------------------------------- 1 | use super::{Mapper, MapperParams, RomAddress}; 2 | use super::bank::*; 3 | use super::battery::BatteryBackedRam; 4 | use super::volatile::VolatileRam; 5 | use cart::ScreenMode; 6 | use memory::MemSegment; 7 | 8 | #[derive(Debug, Clone, PartialEq)] 9 | struct Ctrl { 10 | mode: PrgMode, 11 | mirroring: &'static [u16; 4], // TODO: Add chr mode 12 | } 13 | 14 | #[derive(Debug, Clone, PartialEq)] 15 | enum PrgMode { 16 | Switch32Kb, 17 | FixFirst, 18 | FixLast, 19 | } 20 | 21 | #[derive(Debug, Clone, PartialEq)] 22 | struct Regs { 23 | control: Ctrl, 24 | 25 | chr_0: u8, 26 | chr_1: u8, 27 | prg_bank: usize, 28 | } 29 | 30 | struct MMC1 { 31 | regs: Regs, 32 | 33 | accumulator: u8, 34 | write_counter: u8, 35 | 36 | prg_rom: MappingTable, 37 | chr_ram: Box<[u8]>, 38 | prg_ram: Box, 39 | } 40 | 41 | impl MMC1 { 42 | fn update_mapping(&mut self) { 43 | match self.regs.control.mode { 44 | PrgMode::Switch32Kb => self.prg_rom 45 | .map_pages_linear(0..8, (self.regs.prg_bank & 0b0000_1110) * 8), 46 | PrgMode::FixFirst => { 47 | self.prg_rom.map_pages_linear(0..4, 0); 48 | self.prg_rom 49 | .map_pages_linear(4..8, (self.regs.prg_bank & 0b0000_1111) * 4); 50 | } 51 | PrgMode::FixLast => { 52 | self.prg_rom 53 | .map_pages_linear(0..4, (self.regs.prg_bank & 0b0000_1111) * 4); 54 | let bank_count = self.prg_rom.bank_count(); 55 | self.prg_rom.map_pages_linear(4..8, bank_count - 4); 56 | } 57 | } 58 | } 59 | 60 | fn reset(&mut self) { 61 | self.accumulator = 0; 62 | self.write_counter = 0; 63 | self.regs.control = Ctrl { 64 | mode: PrgMode::FixLast, 65 | mirroring: super::standard_mapping_tables(ScreenMode::OneScreenLow), 66 | }; 67 | self.update_mapping(); 68 | } 69 | 70 | fn do_write(&mut self, idx: u16) { 71 | match idx { 72 | 0x8000...0x9FFF => { 73 | let val = self.accumulator; 74 | let mode = match (val & 0x0C) >> 2 { 75 | 0 | 1 => PrgMode::Switch32Kb, 76 | 2 => PrgMode::FixFirst, 77 | 3 => PrgMode::FixLast, 78 | _ => panic!("Can't happen."), 79 | }; 80 | let mirroring = match val & 0x03 { 81 | 0 => ScreenMode::OneScreenLow, 82 | 1 => ScreenMode::OneScreenHigh, 83 | 2 => ScreenMode::Vertical, 84 | 3 => ScreenMode::Horizontal, 85 | _ => panic!("Can't happen."), 86 | }; 87 | self.regs.control = Ctrl { 88 | mode: mode, 89 | mirroring: super::standard_mapping_tables(mirroring), 90 | }; 91 | } 92 | 0xA000...0xBFFF => self.regs.chr_0 = self.accumulator, 93 | 0xC000...0xDFFF => self.regs.chr_1 = self.accumulator, 94 | 0xE000...0xFFFF => self.regs.prg_bank = self.accumulator as usize, 95 | x => invalid_address!(x), 96 | } 97 | self.update_mapping(); 98 | } 99 | } 100 | 101 | fn prg_ram_addr(idx: u16) -> u16 { 102 | idx - 0x6000 103 | } 104 | 105 | pub fn new(params: MapperParams) -> Box { 106 | let chr_ram = if params.chr_rom.is_empty() { 107 | vec![0u8; 0x2000].into_boxed_slice() 108 | } else { 109 | vec![0u8; 0].into_boxed_slice() 110 | }; 111 | 112 | let prg_ram: Box = if params.has_battery_backed_ram { 113 | Box::new( 114 | BatteryBackedRam::new(params.rom_path, params.prg_ram_size as u32).unwrap(), 115 | ) 116 | } else { 117 | Box::new(VolatileRam::new(params.prg_ram_size as usize)) 118 | }; 119 | 120 | let mut mapper = MMC1 { 121 | regs: Regs { 122 | control: Ctrl { 123 | mode: PrgMode::FixLast, 124 | mirroring: super::standard_mapping_tables(ScreenMode::OneScreenLow), 125 | }, 126 | chr_0: 0, 127 | chr_1: 0, 128 | prg_bank: 0, 129 | }, 130 | accumulator: 0, 131 | write_counter: 0, 132 | prg_rom: MappingTable::new(params.prg_rom, 4), 133 | chr_ram: chr_ram, 134 | prg_ram: prg_ram, 135 | }; 136 | mapper.update_mapping(); 137 | 138 | Box::new(mapper) 139 | } 140 | 141 | impl Mapper for MMC1 { 142 | fn prg_rom_read(&mut self, idx: u16) -> &RomBank { 143 | self.prg_rom.get_bank(idx) 144 | } 145 | 146 | fn prg_rom_write(&mut self, idx: u16, val: u8) -> &mut RomBank { 147 | if val & 0b1000_0000 != 0 { 148 | self.reset(); 149 | } else { 150 | self.accumulator |= (val & 1) << self.write_counter; 151 | self.write_counter += 1; 152 | 153 | if self.write_counter == 5 { 154 | self.do_write(idx); 155 | self.accumulator = 0; 156 | self.write_counter = 0; 157 | } 158 | } 159 | 160 | self.prg_rom.get_bank_mut(idx) 161 | } 162 | 163 | fn prg_rom_address(&self, idx: u16) -> RomAddress { 164 | self.prg_rom.get_rom_address(idx) 165 | } 166 | 167 | fn prg_ram_read(&mut self, idx: u16) -> u8 { 168 | self.prg_ram.read(prg_ram_addr(idx)) 169 | } 170 | 171 | fn prg_ram_write(&mut self, idx: u16, val: u8) { 172 | self.prg_ram.write(prg_ram_addr(idx), val); 173 | } 174 | 175 | fn chr_read(&mut self, idx: u16) -> u8 { 176 | self.chr_ram[idx as usize] 177 | } 178 | 179 | fn chr_write(&mut self, idx: u16, val: u8) { 180 | self.chr_ram[idx as usize] = val; 181 | } 182 | 183 | fn get_mirroring_table(&self) -> &[u16; 4] { 184 | self.regs.control.mirroring 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod hash_screen; 2 | mod test_io; 3 | mod bench; 4 | 5 | use Settings; 6 | use std::collections::HashMap; 7 | use std::path::Path; 8 | 9 | #[test] 10 | fn verify_completes_nestest() { 11 | let mut hashes: HashMap = HashMap::new(); 12 | let mut commands: HashMap = HashMap::new(); 13 | 14 | // Run the main tests 15 | commands.insert(10, "....T...|........"); 16 | hashes.insert(35, "2bfe5ffe2fae65fa730c04735a3b25115c5fb65e"); 17 | 18 | // Switch to the unofficial opcode tests and run them 19 | commands.insert(40, ".....S..|........"); 20 | commands.insert(45, "....T...|........"); 21 | hashes.insert(65, "0b6895e6ff0e8be76e805a067be6ebec89e7d6ad"); 22 | 23 | run_system_test( 24 | 70, 25 | Path::new("nes-test-roms/other/nestest.nes"), 26 | hashes, 27 | commands, 28 | ); 29 | } 30 | 31 | #[test] 32 | fn blargg_apu_test_len_ctr() { 33 | let mut hashes: HashMap = HashMap::new(); 34 | let commands: HashMap = HashMap::new(); 35 | 36 | hashes.insert(18, "ea9ac1696a5cec416f0a9f34c052815ca59850d5"); 37 | 38 | run_system_test( 39 | 19, 40 | Path::new("nes-test-roms/apu_test/rom_singles/1-len_ctr.nes"), 41 | hashes, 42 | commands, 43 | ); 44 | } 45 | 46 | #[test] 47 | fn blargg_apu_test_len_table() { 48 | let mut hashes: HashMap = HashMap::new(); 49 | let commands: HashMap = HashMap::new(); 50 | 51 | hashes.insert(13, "90a61bd003c5794713aa5f207b9b70c8862d892b"); 52 | 53 | run_system_test( 54 | 14, 55 | Path::new("nes-test-roms/apu_test/rom_singles/2-len_table.nes"), 56 | hashes, 57 | commands, 58 | ); 59 | } 60 | 61 | #[test] 62 | fn blargg_apu_test_irq_flag() { 63 | let mut hashes: HashMap = HashMap::new(); 64 | let commands: HashMap = HashMap::new(); 65 | 66 | hashes.insert(18, "09e4ad012c8fddfd8e3b4cc6d1b395c5062768c2"); 67 | 68 | run_system_test( 69 | 19, 70 | Path::new("nes-test-roms/apu_test/rom_singles/3-irq_flag.nes"), 71 | hashes, 72 | commands, 73 | ); 74 | } 75 | 76 | #[test] 77 | fn blargg_ppu_test_palette_ram() { 78 | let mut hashes: HashMap = HashMap::new(); 79 | let commands: HashMap = HashMap::new(); 80 | 81 | hashes.insert(18, "cb15f68f631c1d409beefb775bcff990286096fb"); 82 | 83 | run_system_test( 84 | 19, 85 | Path::new("nes-test-roms/blargg_ppu_tests_2005.09.15b/palette_ram.nes"), 86 | hashes, 87 | commands, 88 | ); 89 | } 90 | 91 | #[test] 92 | fn blargg_ppu_test_sprite_ram() { 93 | let mut hashes: HashMap = HashMap::new(); 94 | let commands: HashMap = HashMap::new(); 95 | 96 | hashes.insert(18, "cb15f68f631c1d409beefb775bcff990286096fb"); 97 | 98 | run_system_test( 99 | 19, 100 | Path::new("nes-test-roms/blargg_ppu_tests_2005.09.15b/sprite_ram.nes"), 101 | hashes, 102 | commands, 103 | ); 104 | } 105 | 106 | #[test] 107 | fn blargg_ppu_test_vram_access() { 108 | let mut hashes: HashMap = HashMap::new(); 109 | let commands: HashMap = HashMap::new(); 110 | 111 | hashes.insert(18, "cb15f68f631c1d409beefb775bcff990286096fb"); 112 | 113 | run_system_test( 114 | 19, 115 | Path::new("nes-test-roms/blargg_ppu_tests_2005.09.15b/vram_access.nes"), 116 | hashes, 117 | commands, 118 | ); 119 | } 120 | 121 | #[test] 122 | fn oam_read() { 123 | let mut hashes: HashMap = HashMap::new(); 124 | let commands: HashMap = HashMap::new(); 125 | 126 | hashes.insert(27, "cc2447362cceb400803a18c2e4b5d5d4e4aa2ea7"); 127 | 128 | run_system_test( 129 | 28, 130 | Path::new("nes-test-roms/oam_read/oam_read.nes"), 131 | hashes, 132 | commands, 133 | ); 134 | } 135 | 136 | #[test] 137 | fn sprite_hit_basics() { 138 | let mut hashes: HashMap = HashMap::new(); 139 | let commands: HashMap = HashMap::new(); 140 | 141 | hashes.insert(33, "1437c48bb22dd3be0d37449171d2120e13877326"); 142 | 143 | run_system_test( 144 | 33, 145 | Path::new("nes-test-roms/sprite_hit_tests_2005.10.05/01.basics.nes"), 146 | hashes, 147 | commands, 148 | ); 149 | } 150 | 151 | #[test] 152 | fn sprite_hit_alignment() { 153 | let mut hashes: HashMap = HashMap::new(); 154 | let commands: HashMap = HashMap::new(); 155 | 156 | hashes.insert(31, "33815f5682dda683d1a9fe7495f6358c0e741a9d"); 157 | 158 | run_system_test( 159 | 32, 160 | Path::new("nes-test-roms/sprite_hit_tests_2005.10.05/02.alignment.nes"), 161 | hashes, 162 | commands, 163 | ); 164 | } 165 | 166 | #[test] 167 | fn sprite_hit_corners() { 168 | let mut hashes: HashMap = HashMap::new(); 169 | let commands: HashMap = HashMap::new(); 170 | 171 | hashes.insert(21, "760203cab0bc4df16bda48438f67a91e8a152fb9"); 172 | 173 | run_system_test( 174 | 22, 175 | Path::new("nes-test-roms/sprite_hit_tests_2005.10.05/03.corners.nes"), 176 | hashes, 177 | commands, 178 | ); 179 | } 180 | 181 | #[test] 182 | fn sprite_hit_flip() { 183 | let mut hashes: HashMap = HashMap::new(); 184 | let commands: HashMap = HashMap::new(); 185 | 186 | hashes.insert(21, "e16e43e5efdeacfd999a8ea031fa5058ec202f96"); 187 | 188 | run_system_test( 189 | 22, 190 | Path::new("nes-test-roms/sprite_hit_tests_2005.10.05/04.flip.nes"), 191 | hashes, 192 | commands, 193 | ); 194 | } 195 | 196 | fn run_system_test( 197 | frames: u32, 198 | file_name: &Path, 199 | hashes: HashMap, 200 | commands: HashMap, 201 | ) { 202 | 203 | let cart = ::cart::Cart::read(file_name).expect("Failed to read ROM File"); 204 | let settings = Settings { 205 | jit: true, 206 | ..Default::default() 207 | }; 208 | let mut builder = ::EmulatorBuilder::new(cart, settings); 209 | builder.io = Box::new(test_io::TestIO::new(commands)); 210 | builder.screen = Box::new(hash_screen::HashVerifier::new(hashes)); 211 | builder.screen = Box::new(hash_screen::HashPrinter::new(builder.screen)); 212 | 213 | let mut emulator = builder.build(); 214 | 215 | for _ in 0..frames { 216 | assert!(!emulator.halted()); 217 | emulator.run_frame(); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/ppu/ppu_memory.rs: -------------------------------------------------------------------------------- 1 | use super::Color; 2 | use super::TilePattern; 3 | use cart::Cart; 4 | use memory::MemSegment; 5 | use std::cell::UnsafeCell; 6 | use std::rc::Rc; 7 | 8 | /// Represents the PPU's memory map. 9 | pub struct PPUMemory { 10 | cart: Rc>, 11 | vram: Box<[u8; 0x0F00]>, 12 | palette: [Color; 0x20], 13 | } 14 | 15 | impl PPUMemory { 16 | pub fn new(cart: Rc>) -> PPUMemory { 17 | PPUMemory { 18 | cart: cart, 19 | vram: Box::new([0u8; 0x0F00]), 20 | palette: [Color::from_bits_truncate(0); 0x20], 21 | } 22 | } 23 | } 24 | 25 | fn get_tile_addr(tile_id: u8, plane: u8, fine_y_scroll: u16, tile_table: u16) -> u16 { 26 | let mut tile_addr = 0u16; 27 | tile_addr |= fine_y_scroll; 28 | tile_addr |= plane as u16; // Plane must be 0 for low or 8 for high 29 | tile_addr |= (tile_id as u16) << 4; 30 | tile_addr |= tile_table; // Table must be 0x0000 or 0x1000 31 | tile_addr 32 | } 33 | 34 | impl PPUMemory { 35 | pub fn read_bypass_palette(&mut self, idx: u16) -> u8 { 36 | let idx = self.translate_vram_address(idx); 37 | self.vram[idx] 38 | } 39 | 40 | fn translate_vram_address(&self, idx: u16) -> usize { 41 | let idx = idx & 0x0FFF; 42 | let nametable_num = (idx / 0x0400) as usize; 43 | let idx_in_nametable = idx % 0x400; 44 | let table: &[u16; 4] = unsafe { (*self.cart.get()).get_mirroring_table() }; 45 | let translated = table[nametable_num] + idx_in_nametable; 46 | translated as usize % self.vram.len() 47 | } 48 | 49 | #[cfg(feature = "vectorize")] 50 | pub fn get_palettes(&self) -> (::simd::u8x16, ::simd::u8x16) { 51 | let palette_bytes: &[u8; 0x20] = unsafe { ::std::mem::transmute(&self.palette) }; 52 | ( 53 | ::simd::u8x16::load(palette_bytes, 0), 54 | ::simd::u8x16::load(palette_bytes, 16), 55 | ) 56 | } 57 | 58 | #[cfg(not(feature = "vectorize"))] 59 | pub fn read_palette(&self, idx: super::PaletteIndex) -> Color { 60 | self.palette[idx.to_index()] 61 | } 62 | 63 | pub fn read_tile_pattern( 64 | &mut self, 65 | tile_id: u8, 66 | fine_y_scroll: u16, 67 | tile_table: u16, 68 | ) -> TilePattern { 69 | let lo_addr = get_tile_addr(tile_id, 0, fine_y_scroll, tile_table); 70 | let hi_addr = get_tile_addr(tile_id, 8, fine_y_scroll, tile_table); 71 | TilePattern { 72 | lo: self.read(lo_addr), 73 | hi: self.read(hi_addr), 74 | } 75 | } 76 | 77 | #[allow(dead_code)] 78 | pub fn dump_nametable(&mut self, idx: u16) { 79 | let start_idx = 0x2000 + (idx * 0x400); 80 | println!("Nametable {}:", idx); 81 | self.print_columns(start_idx..(start_idx + 0x3C0), 32) 82 | } 83 | 84 | #[allow(dead_code)] 85 | pub fn dump_attribute_table(&mut self, idx: u16) { 86 | let start_idx = 0x2000 + (idx * 0x400); 87 | println!("Attribute table {}:", idx); 88 | self.print_columns((start_idx + 0x3C0)..(start_idx + 0x400), 32); 89 | } 90 | } 91 | 92 | impl MemSegment for PPUMemory { 93 | fn read(&mut self, idx: u16) -> u8 { 94 | match idx { 95 | 0x0000...0x1FFF => unsafe { (*self.cart.get()).chr_read(idx) }, 96 | 0x2000...0x3EFF => self.read_bypass_palette(idx), 97 | 0x3F00...0x3FFF => self.palette[(idx & 0x1F) as usize].bits(), 98 | x => invalid_address!(x), 99 | } 100 | } 101 | 102 | fn write(&mut self, idx: u16, val: u8) { 103 | match idx { 104 | 0x0000...0x1FFF => unsafe { (*self.cart.get()).chr_write(idx, val) }, 105 | 0x2000...0x3EFF => { 106 | let idx = self.translate_vram_address(idx); 107 | self.vram[idx] = val; 108 | } 109 | 0x3F00...0x3FFF => { 110 | let val = Color::from_bits_truncate(val); 111 | let idx = (idx & 0x001F) as usize; 112 | // Do the palette mirroring on write since we read a lot more than we write. 113 | // This is not strictly accurate - the PPU can actually render these colors 114 | // in certain rare circumstances - but it's good enough. 115 | match idx { 116 | 0x10 => self.palette[0x00] = val, 117 | 0x00 => self.palette[0x10] = val, 118 | 119 | 0x14 => self.palette[0x04] = val, 120 | 0x04 => self.palette[0x14] = val, 121 | 122 | 0x18 => self.palette[0x08] = val, 123 | 0x08 => self.palette[0x18] = val, 124 | 125 | 0x1C => self.palette[0x0C] = val, 126 | 0x0C => self.palette[0x1C] = val, 127 | 128 | _ => (), 129 | }; 130 | self.palette[idx] = val; 131 | } 132 | x => invalid_address!(x), 133 | } 134 | } 135 | } 136 | 137 | #[cfg(test)] 138 | mod tests { 139 | use cart::ScreenMode; 140 | use memory::MemSegment; 141 | use ppu::{Color, PPU}; 142 | use ppu::tests::*; 143 | 144 | #[test] 145 | fn ppu_can_read_write_palette() { 146 | let mut ppu = create_test_ppu(); 147 | 148 | ppu.reg.v = 0x3F00; 149 | ppu.write(0x2007, 12); 150 | ppu.reg.v = 0x3F00; 151 | assert_eq!(ppu.ppu_mem.palette[0], Color::from_bits_truncate(12)); 152 | 153 | ppu.reg.v = 0x3F01; 154 | ppu.write(0x2007, 212); 155 | ppu.reg.v = 0x3F01; 156 | assert_eq!(ppu.read(0x2007), 212 & 0x3F); 157 | } 158 | 159 | #[test] 160 | fn test_palette_mirroring() { 161 | let mut ppu = create_test_ppu(); 162 | 163 | let mirrors = [0x3F10, 0x3F14, 0x3F18, 0x3F1C]; 164 | let targets = [0x3F00, 0x3F04, 0x3F08, 0x3F0C]; 165 | for x in 0..4 { 166 | 167 | ppu.reg.v = targets[x]; 168 | ppu.write(0x2007, 12); 169 | ppu.reg.v = mirrors[x]; 170 | assert_eq!(ppu.read(0x2007), 12); 171 | 172 | ppu.reg.v = mirrors[x]; 173 | ppu.write(0x2007, 12); 174 | ppu.reg.v = targets[x]; 175 | assert_eq!(ppu.read(0x2007), 12); 176 | } 177 | } 178 | 179 | fn to_nametable_idx(idx: u16, tbl: u16) -> u16 { 180 | 0x2000 + (0x0400 * tbl) + idx 181 | } 182 | 183 | fn assert_mirrored(ppu: &mut PPU, tbl1: u16, tbl2: u16) { 184 | for idx in 0x0000..0x0400 { 185 | let tbl1_idx = to_nametable_idx(idx, tbl1); 186 | let tbl2_idx = to_nametable_idx(idx, tbl2); 187 | 188 | println!( 189 | "Translated: tbl1: {:04X}, tbl2: {:04X}", 190 | ppu.ppu_mem.translate_vram_address(tbl1_idx), 191 | ppu.ppu_mem.translate_vram_address(tbl2_idx), 192 | ); 193 | 194 | ppu.ppu_mem.write(tbl1_idx, 0xFF); 195 | assert_eq!(0xFF, ppu.ppu_mem.read(tbl2_idx)); 196 | 197 | ppu.ppu_mem.write(tbl2_idx, 0x61); 198 | assert_eq!(0x61, ppu.ppu_mem.read(tbl1_idx)); 199 | } 200 | } 201 | 202 | fn assert_not_mirrored(ppu: &mut PPU, tbl1: u16, tbl2: u16) { 203 | for idx in 0x0000..0x0400 { 204 | let tbl1_idx = to_nametable_idx(idx, tbl1); 205 | let tbl2_idx = to_nametable_idx(idx, tbl2); 206 | 207 | println!( 208 | "Translated: tbl1: {:04X}, tbl2: {:04X}", 209 | ppu.ppu_mem.translate_vram_address(tbl1_idx), 210 | ppu.ppu_mem.translate_vram_address(tbl2_idx), 211 | ); 212 | 213 | ppu.ppu_mem.write(tbl1_idx, 0x00); 214 | ppu.ppu_mem.write(tbl2_idx, 0x00); 215 | 216 | ppu.ppu_mem.write(tbl1_idx, 0xFF); 217 | assert_eq!(0x00, ppu.ppu_mem.read(tbl2_idx)); 218 | 219 | ppu.ppu_mem.write(tbl2_idx, 0x61); 220 | assert_eq!(0xFF, ppu.ppu_mem.read(tbl1_idx)); 221 | } 222 | } 223 | 224 | #[test] 225 | fn single_screen_mirroring_mirrors_both_ways() { 226 | let mut ppu = create_test_ppu_with_mirroring(ScreenMode::OneScreenLow); 227 | 228 | assert_mirrored(&mut ppu, 0, 1); 229 | assert_mirrored(&mut ppu, 1, 2); 230 | assert_mirrored(&mut ppu, 2, 3); 231 | } 232 | 233 | #[test] 234 | fn four_screen_mirroring_mirrors_both_ways() { 235 | let mut ppu = create_test_ppu_with_mirroring(ScreenMode::FourScreen); 236 | 237 | assert_not_mirrored(&mut ppu, 0, 1); 238 | assert_not_mirrored(&mut ppu, 1, 2); 239 | assert_not_mirrored(&mut ppu, 2, 3); 240 | } 241 | 242 | #[test] 243 | fn horizontal_mirroring_mirrors_horizontally() { 244 | let mut ppu = create_test_ppu_with_mirroring(ScreenMode::Horizontal); 245 | 246 | assert_mirrored(&mut ppu, 0, 1); 247 | assert_mirrored(&mut ppu, 2, 3); 248 | assert_not_mirrored(&mut ppu, 0, 2); 249 | assert_not_mirrored(&mut ppu, 1, 3); 250 | } 251 | 252 | #[test] 253 | fn vertical_mirroring_mirrors_vertically() { 254 | let mut ppu = create_test_ppu_with_mirroring(ScreenMode::Vertical); 255 | 256 | assert_not_mirrored(&mut ppu, 0, 1); 257 | assert_not_mirrored(&mut ppu, 2, 3); 258 | assert_mirrored(&mut ppu, 0, 2); 259 | assert_mirrored(&mut ppu, 1, 3); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/apu/mod.rs: -------------------------------------------------------------------------------- 1 | mod components; 2 | mod buffer; 3 | mod square; 4 | mod triangle; 5 | mod noise; 6 | mod dmc; 7 | 8 | use Settings; 9 | use apu::buffer::*; 10 | use apu::dmc::*; 11 | use apu::noise::*; 12 | use apu::square::*; 13 | use apu::triangle::*; 14 | use audio::AudioOut; 15 | use cpu::IrqInterrupt; 16 | use std::cell::RefCell; 17 | use std::cmp; 18 | use std::rc::Rc; 19 | 20 | pub type Sample = i16; 21 | 22 | #[allow(zero_prefixed_literal)] 23 | static NTSC_TICK_LENGTH_TABLE: [[u64; 6]; 2] = [ 24 | [7459, 7456, 7458, 7458, 7458, 0000], 25 | [0001, 7458, 7456, 7458, 7458, 7452], 26 | ]; 27 | 28 | const VOLUME_MULT: i32 = ((32767i16 / 16) / 3) as i32; 29 | 30 | bitflags! { 31 | struct Frame : u8 { 32 | const MODE = 0b1000_0000; //0 = 4-step, 1 = 5-step 33 | const SUPPRESS_IRQ = 0b0100_0000; //0 = disabled, 1 = enabled 34 | } 35 | } 36 | 37 | impl Frame { 38 | fn mode(&self) -> usize { 39 | if self.contains(MODE) { 40 | 1 41 | } else { 42 | 0 43 | } 44 | } 45 | } 46 | 47 | trait Writable { 48 | fn write(&mut self, idx: u16, val: u8); 49 | } 50 | 51 | enum Jitter { 52 | Delay(u64, u8), 53 | None, 54 | } 55 | 56 | pub struct APU { 57 | settings: Rc, 58 | 59 | square1: Square, 60 | square2: Square, 61 | triangle: Triangle, 62 | noise: Noise, 63 | dmc: DMC, 64 | frame: Frame, 65 | 66 | square_buffer: Rc>, 67 | tnd_buffer: Rc>, 68 | 69 | device: Box, 70 | 71 | global_cyc: u64, 72 | tick: u8, 73 | next_tick_cyc: u64, 74 | next_transfer_cyc: u64, 75 | last_frame_cyc: u64, 76 | 77 | irq_requested: bool, 78 | 79 | jitter: Jitter, 80 | } 81 | 82 | impl APU { 83 | pub fn new(settings: Rc, device: Box) -> APU { 84 | let sample_rate = device.sample_rate(); 85 | 86 | let square_buffer = Rc::new(RefCell::new(SampleBuffer::new(sample_rate))); 87 | let tnd_buffer = Rc::new(RefCell::new(SampleBuffer::new(sample_rate))); 88 | let clocks_needed = square_buffer.borrow().clocks_needed() as u64; 89 | 90 | APU { 91 | settings: settings, 92 | square1: Square::new(false, Waveform::new(square_buffer.clone(), VOLUME_MULT)), 93 | square2: Square::new(true, Waveform::new(square_buffer.clone(), VOLUME_MULT)), 94 | triangle: Triangle::new(Waveform::new(tnd_buffer.clone(), VOLUME_MULT)), 95 | noise: Noise::new(Waveform::new(tnd_buffer.clone(), VOLUME_MULT)), 96 | dmc: DMC::new(), 97 | frame: Frame::empty(), 98 | 99 | square_buffer: square_buffer, 100 | tnd_buffer: tnd_buffer, 101 | 102 | device: device, 103 | 104 | global_cyc: 0, 105 | tick: 0, 106 | next_tick_cyc: NTSC_TICK_LENGTH_TABLE[0][0], 107 | next_transfer_cyc: clocks_needed, 108 | last_frame_cyc: 0, 109 | 110 | irq_requested: false, 111 | 112 | jitter: Jitter::None, 113 | } 114 | } 115 | 116 | pub fn run_to(&mut self, cpu_cycle: u64) -> IrqInterrupt { 117 | let mut interrupt = IrqInterrupt::None; 118 | 119 | while self.global_cyc < cpu_cycle { 120 | let current_cycle = self.global_cyc; 121 | 122 | let mut next_step = cmp::min(cpu_cycle, self.next_tick_cyc); 123 | next_step = cmp::min(next_step, self.next_transfer_cyc); 124 | 125 | if let Jitter::Delay(time, _) = self.jitter { 126 | next_step = cmp::min(next_step, time); 127 | } 128 | 129 | if self.settings.sound_enabled { 130 | self.play(current_cycle, next_step); 131 | } 132 | self.global_cyc = next_step; 133 | 134 | if let Jitter::Delay(time, val) = self.jitter { 135 | if self.global_cyc == time { 136 | self.set_4017(val); 137 | self.jitter = Jitter::None; 138 | } 139 | } 140 | if self.global_cyc == self.next_tick_cyc { 141 | interrupt = interrupt.or(self.tick()); 142 | } 143 | if self.global_cyc == self.next_transfer_cyc { 144 | self.transfer(); 145 | } 146 | } 147 | interrupt 148 | } 149 | 150 | /// Represents the 240Hz output of the frame sequencer's divider 151 | fn tick(&mut self) -> IrqInterrupt { 152 | self.tick += 1; 153 | let mode = self.frame.mode(); 154 | self.next_tick_cyc = self.global_cyc + NTSC_TICK_LENGTH_TABLE[mode][self.tick as usize]; 155 | 156 | match mode { 157 | 0 => { 158 | match self.tick { 159 | 1 => { 160 | self.envelope_tick(); 161 | } 162 | 2 => { 163 | self.envelope_tick(); 164 | self.length_tick(); 165 | } 166 | 3 => { 167 | self.envelope_tick(); 168 | } 169 | 4 => { 170 | self.tick = 0; 171 | self.envelope_tick(); 172 | self.length_tick(); 173 | return self.raise_irq(); 174 | } 175 | _ => { 176 | self.tick = 0; 177 | } 178 | } 179 | } 180 | 1 => { 181 | match self.tick { 182 | 1 => { 183 | self.envelope_tick(); 184 | self.length_tick() 185 | } 186 | 2 => { 187 | self.envelope_tick(); 188 | } 189 | 3 => { 190 | self.envelope_tick(); 191 | self.length_tick() 192 | } 193 | 4 => { 194 | self.envelope_tick(); 195 | } 196 | 5 => { 197 | self.tick = 0; 198 | } //4 is the actual last tick in the cycle. 199 | _ => { 200 | self.tick = 0; 201 | } 202 | } 203 | } 204 | _ => (), 205 | } 206 | IrqInterrupt::None 207 | } 208 | 209 | fn envelope_tick(&mut self) { 210 | self.square1.envelope_tick(); 211 | self.square2.envelope_tick(); 212 | self.triangle.envelope_tick(); 213 | self.noise.envelope_tick(); 214 | } 215 | 216 | fn length_tick(&mut self) { 217 | self.square1.length_tick(); 218 | self.square2.length_tick(); 219 | self.triangle.length_tick(); 220 | self.noise.length_tick(); 221 | } 222 | 223 | fn raise_irq(&mut self) -> IrqInterrupt { 224 | if !self.frame.contains(SUPPRESS_IRQ) { 225 | self.irq_requested = true; 226 | IrqInterrupt::IRQ 227 | } else { 228 | IrqInterrupt::None 229 | } 230 | } 231 | 232 | fn play(&mut self, from_cyc: u64, to_cyc: u64) { 233 | let from = (from_cyc - self.last_frame_cyc) as u32; 234 | let to = (to_cyc - self.last_frame_cyc) as u32; 235 | self.square1.play(from, to); 236 | self.square2.play(from, to); 237 | self.triangle.play(from, to); 238 | self.noise.play(from, to); 239 | self.dmc.play(from, to); 240 | } 241 | 242 | fn transfer(&mut self) { 243 | let cpu_cyc = self.global_cyc; 244 | let cycles_since_last_frame = (cpu_cyc - self.last_frame_cyc) as u32; 245 | self.last_frame_cyc = cpu_cyc; 246 | 247 | if self.settings.sound_enabled { 248 | let mut square_buf = self.square_buffer.borrow_mut(); 249 | let mut tnd_buf = self.tnd_buffer.borrow_mut(); 250 | tnd_buf.end_frame(cycles_since_last_frame); 251 | square_buf.end_frame(cycles_since_last_frame); 252 | let samples: Vec = { 253 | let iter1 = square_buf.read().iter().cloned(); 254 | let iter2 = tnd_buf.read().iter().cloned(); 255 | iter1.zip(iter2).map(|(s, t)| s.saturating_add(t)).collect() 256 | }; 257 | self.next_transfer_cyc = cpu_cyc + square_buf.clocks_needed() as u64; 258 | self.device.play(&samples); 259 | } else { 260 | self.next_transfer_cyc += 100000000; 261 | } 262 | } 263 | 264 | /// Returns the cycle number representing the next time the CPU should run 265 | /// the APU. 266 | /// Min of the next APU IRQ, the next DMC IRQ, and the next tick time. When 267 | /// the CPU cycle reaches 268 | /// this number, the CPU must run the APU. 269 | pub fn requested_run_cycle(&self) -> u64 { 270 | // In practice, the next tick time should cover the APU IRQ as well, since the 271 | // IRQ happens on tick boundaries. The DMC IRQ isn't implemented yet. 272 | // Using the tick time ensures that the APU will never get too far behind the 273 | // CPU. 274 | self.next_tick_cyc 275 | } 276 | 277 | fn set_4017(&mut self, val: u8) { 278 | self.frame = Frame::from_bits_truncate(val); 279 | if self.frame.contains(SUPPRESS_IRQ) { 280 | self.irq_requested = false; 281 | } 282 | 283 | self.tick = 0; 284 | self.next_tick_cyc = self.global_cyc + NTSC_TICK_LENGTH_TABLE[self.frame.mode()][0]; 285 | } 286 | 287 | #[cfg_attr(rustfmt, rustfmt_skip)] 288 | pub fn read_status(&mut self, cycle: u64) -> (IrqInterrupt, u8) { 289 | let interrupt = self.run_to(cycle - 1); 290 | 291 | let mut status: u8 = 0; 292 | status |= self.square1.length.active(); 293 | status |= self.square2.length.active() << 1; 294 | status |= self.triangle.length.active() << 2; 295 | status |= self.noise.length.active() << 3; 296 | status |= if self.irq_requested { 1 << 6 } else { 0 }; 297 | self.irq_requested = false; 298 | 299 | (interrupt.or(self.run_to(cycle)), status) 300 | } 301 | 302 | pub fn write(&mut self, idx: u16, val: u8) { 303 | match idx % 0x20 { 304 | x @ 0x00...0x03 => self.square1.write(x, val), 305 | x @ 0x04...0x07 => self.square2.write(x, val), 306 | x @ 0x08...0x0B => self.triangle.write(x, val), 307 | x @ 0x0C...0x0F => self.noise.write(x, val), 308 | x @ 0x10...0x13 => self.dmc.write(x, val), 309 | 0x0015 => { 310 | self.noise.length.set_enable(val & 0b0000_1000 != 0); 311 | self.triangle.length.set_enable(val & 0b0000_0100 != 0); 312 | self.square2.length.set_enable(val & 0b0000_0010 != 0); 313 | self.square1.length.set_enable(val & 0b0000_0001 != 0); 314 | } 315 | 0x0017 => { 316 | if self.global_cyc % 2 == 0 { 317 | self.set_4017(val); 318 | } else { 319 | self.jitter = Jitter::Delay(self.global_cyc + 1, val); 320 | } 321 | } 322 | _ => (), 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/ppu/ppu_reg.rs: -------------------------------------------------------------------------------- 1 | use memory::MemSegment; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq)] 4 | pub enum AddrByte { 5 | High, 6 | Low, 7 | } 8 | 9 | pub struct PPUCtrl { 10 | bits: u8, 11 | } 12 | impl PPUCtrl { 13 | pub fn empty() -> PPUCtrl { 14 | PPUCtrl { bits: 0 } 15 | } 16 | 17 | pub fn new(bits: u8) -> PPUCtrl { 18 | PPUCtrl { bits: bits } 19 | } 20 | 21 | pub fn vram_addr_step(&self) -> u16 { 22 | if self.bits & 0b0000_0100 != 0 { 32 } else { 1 } 23 | } 24 | 25 | pub fn background_table(&self) -> u16 { 26 | if self.bits & 0b0001_0000 != 0 { 27 | 0x1000 28 | } else { 29 | 0x0000 30 | } 31 | } 32 | 33 | pub fn sprite_table(&self) -> u16 { 34 | if self.bits & 0b0010_0000 != 0 { 35 | 0x1000 36 | } else { 37 | 0x0000 38 | } 39 | } 40 | 41 | pub fn generate_vblank_nmi(&self) -> bool { 42 | self.bits & 0b1000_0000 != 0 43 | } 44 | 45 | pub fn tall_sprites(&self) -> bool { 46 | self.bits & 0b0010_0000 != 0 47 | } 48 | 49 | pub fn sprite_height(&self) -> u16 { 50 | if self.tall_sprites() { 16 } else { 8 } 51 | } 52 | } 53 | 54 | bitflags! { 55 | pub struct PPUMask : u8 { 56 | #[allow(dead_code)] 57 | const GREY = 0b0000_0001; //Greyscale 58 | #[allow(dead_code)] 59 | const S_BCK_L = 0b0000_0010; //Show background in the leftmost 8 pixels 60 | #[allow(dead_code)] 61 | const S_SPR_L = 0b0000_0100; //Show sprites in the leftmost 8 pixels 62 | const S_BCK = 0b0000_1000; //Show background 63 | const S_SPR = 0b0001_0000; //Show sprites 64 | #[allow(dead_code)] 65 | const EM_R = 0b0010_0000; //Emphasize Red 66 | #[allow(dead_code)] 67 | const EM_G = 0b0100_0000; //Emphasize Green 68 | #[allow(dead_code)] 69 | const EM_B = 0b1000_0000; //Emphasize Blue 70 | } 71 | } 72 | 73 | impl PPUMask { 74 | pub fn rendering_enabled(&self) -> bool { 75 | self.intersects(S_BCK | S_SPR) 76 | } 77 | } 78 | 79 | bitflags! { 80 | pub struct PPUStat : u8 { 81 | const VBLANK = 0b1000_0000; //Currently in the vertical blank interval 82 | const SPRITE_0 = 0b0100_0000; //Sprite 0 hit 83 | const SPRITE_OVERFLOW = 0b0010_0000; //Greater than 8 sprites on current scanline 84 | } 85 | } 86 | 87 | pub struct PPUReg { 88 | pub ppuctrl: PPUCtrl, 89 | pub ppumask: PPUMask, 90 | pub ppustat: PPUStat, 91 | pub oamaddr: u8, 92 | 93 | pub t: u16, 94 | pub v: u16, 95 | pub x: u8, 96 | 97 | // Normally the scroll values are stored in pieces in t and x. We need the whole values during 98 | // rendering though, so we keep them here to avoid complicated bit-hacking. 99 | scroll_x: u16, 100 | scroll_y: u16, 101 | 102 | /// A fake dynamic latch representing the capacitance of the wires in the 103 | /// PPU that we have to emulate. 104 | dyn_latch: u8, 105 | 106 | /// The address registers are two bytes but we can only write one at a time. 107 | address_latch: AddrByte, 108 | } 109 | 110 | impl PPUReg { 111 | pub fn scroll_x_fine(&self) -> u16 { 112 | self.x as u16 113 | } 114 | 115 | pub fn scroll_y_fine(&self) -> u16 { 116 | self.v >> 12 117 | } 118 | 119 | pub fn incr_ppuaddr(&mut self) { 120 | let incr_size = self.ppuctrl.vram_addr_step(); 121 | self.v = self.v.wrapping_add(incr_size); 122 | } 123 | 124 | pub fn incr_oamaddr(&mut self) { 125 | self.oamaddr = self.oamaddr.wrapping_add(1); 126 | } 127 | 128 | pub fn get_scroll_x(&self) -> u16 { 129 | self.scroll_x 130 | } 131 | 132 | pub fn get_scroll_y(&self) -> u16 { 133 | self.scroll_y 134 | } 135 | 136 | fn set_coarse_x(&mut self, val: u8) { 137 | let coarse_x = val >> 3; 138 | self.t = self.t & 0b111_11_11111_00000 | coarse_x as u16; 139 | } 140 | 141 | fn set_fine_x(&mut self, val: u8) { 142 | self.x = val & 0b0000_0111; 143 | } 144 | 145 | fn set_coarse_y(&mut self, val: u8) { 146 | let coarse_y = val >> 3; 147 | self.t = self.t & 0b111_11_00000_11111 | (coarse_y as u16) << 5; 148 | } 149 | 150 | fn set_fine_y(&mut self, val: u8) { 151 | let fine_y = val & 0b0000_0111; 152 | self.t = self.t & 0b000_11_11111_11111 | (fine_y as u16) << 12; 153 | } 154 | 155 | fn set_addr_high(&mut self, val: u8) { 156 | let addr = val & 0b0011_1111; 157 | self.t = self.t & 0b_0000000_11111111 | (addr as u16) << 8; 158 | } 159 | 160 | fn set_addr_low(&mut self, val: u8) { 161 | self.t = self.t & 0b_1111111_00000000 | val as u16; 162 | } 163 | } 164 | 165 | impl Default for PPUReg { 166 | fn default() -> PPUReg { 167 | PPUReg { 168 | ppuctrl: PPUCtrl::empty(), 169 | ppumask: PPUMask::empty(), 170 | ppustat: PPUStat::empty(), 171 | oamaddr: 0, 172 | t: 0, 173 | v: 0, 174 | x: 0, 175 | scroll_x: 0, 176 | scroll_y: 0, 177 | dyn_latch: 0, 178 | address_latch: AddrByte::High, 179 | } 180 | } 181 | } 182 | 183 | impl MemSegment for PPUReg { 184 | fn read(&mut self, idx: u16) -> u8 { 185 | match idx % 8 { 186 | 0x0000 => self.dyn_latch, 187 | 0x0001 => self.dyn_latch, 188 | 0x0002 => { 189 | self.address_latch = AddrByte::High; 190 | let res = self.ppustat.bits | (self.dyn_latch & 0b0001_1111); 191 | self.ppustat.remove(VBLANK); 192 | res 193 | } 194 | 0x0003 => self.dyn_latch, 195 | 0x0005 => self.dyn_latch, 196 | 0x0006 => self.dyn_latch, 197 | _ => invalid_address!(idx), 198 | } 199 | } 200 | 201 | fn write(&mut self, idx: u16, val: u8) { 202 | self.dyn_latch = val; 203 | match idx % 8 { 204 | 0x0000 => { 205 | self.ppuctrl = PPUCtrl::new(val & 0b1111_1100); 206 | self.t = (self.t & 0b1110011_11111111) | ((val & 0b0000_0011) as u16) << 10; 207 | } 208 | 0x0001 => { 209 | self.ppumask = PPUMask::from_bits_truncate(val); 210 | } 211 | 0x0002 => (), 212 | 0x0003 => self.oamaddr = val, 213 | 0x0005 => { 214 | match self.address_latch { 215 | AddrByte::High => { 216 | self.set_coarse_x(val); 217 | self.set_fine_x(val); 218 | self.scroll_x = val as u16; 219 | self.address_latch = AddrByte::Low; 220 | } 221 | AddrByte::Low => { 222 | self.set_coarse_y(val); 223 | self.set_fine_y(val); 224 | self.scroll_y = val as u16; 225 | self.address_latch = AddrByte::High; 226 | } 227 | } 228 | } 229 | 0x0006 => { 230 | match self.address_latch { 231 | AddrByte::High => { 232 | self.set_addr_high(val); 233 | self.address_latch = AddrByte::Low; 234 | } 235 | AddrByte::Low => { 236 | self.set_addr_low(val); 237 | self.v = self.t; 238 | self.address_latch = AddrByte::High; 239 | } 240 | } 241 | } 242 | _ => invalid_address!(idx), 243 | } 244 | } 245 | } 246 | 247 | #[cfg(test)] 248 | mod tests { 249 | use super::*; 250 | use memory::MemSegment; 251 | use ppu::PPU; 252 | use ppu::tests::*; 253 | 254 | fn assert_register_single_writable(idx: u16, getter: &Fn(&PPU) -> u8) { 255 | let mut ppu = create_test_ppu(); 256 | ppu.write(idx, 12); 257 | assert_eq!(getter(&ppu), 12); 258 | ppu.write(idx, 124); 259 | assert_eq!(getter(&ppu), 124); 260 | } 261 | 262 | fn assert_register_ignores_writes(idx: u16, getter: &Fn(&PPU) -> u8) { 263 | let mut ppu = create_test_ppu(); 264 | ppu.write(idx, 12); 265 | assert_eq!(getter(&ppu), 0); 266 | ppu.write(idx, 124); 267 | assert_eq!(getter(&ppu), 0); 268 | } 269 | 270 | fn assert_writing_register_fills_latch(idx: u16) { 271 | let mut ppu = create_test_ppu(); 272 | ppu.write(idx, 12); 273 | assert_eq!(ppu.reg.dyn_latch, 12); 274 | ppu.write(idx, 124); 275 | assert_eq!(ppu.reg.dyn_latch, 124); 276 | } 277 | 278 | fn assert_register_is_readable(idx: u16, setter: &Fn(&mut PPU, u8) -> ()) { 279 | let mut ppu = create_test_ppu(); 280 | setter(&mut ppu, 12); 281 | assert_eq!(ppu.read(idx), 12); 282 | setter(&mut ppu, 124); 283 | assert_eq!(ppu.read(idx), 124); 284 | } 285 | 286 | fn assert_register_not_readable(idx: u16) { 287 | let mut ppu = create_test_ppu(); 288 | ppu.reg.dyn_latch = 12; 289 | assert_eq!(ppu.read(idx), 12); 290 | ppu.reg.dyn_latch = 124; 291 | assert_eq!(ppu.read(idx), 124); 292 | } 293 | 294 | #[test] 295 | fn ppuctrl_is_write_only_register() { 296 | assert_register_single_writable(0x2000, &|ref ppu| ppu.reg.ppuctrl.bits); 297 | assert_writing_register_fills_latch(0x2000); 298 | assert_register_not_readable(0x2000); 299 | } 300 | 301 | #[test] 302 | fn ppu_mirrors_address() { 303 | assert_register_single_writable(0x2008, &|ref ppu| ppu.reg.ppuctrl.bits); 304 | assert_register_single_writable(0x2010, &|ref ppu| ppu.reg.ppuctrl.bits); 305 | } 306 | 307 | #[test] 308 | fn ppumask_is_write_only_register() { 309 | assert_register_single_writable(0x2001, &|ref ppu| ppu.reg.ppumask.bits()); 310 | assert_writing_register_fills_latch(0x2001); 311 | assert_register_not_readable(0x2001); 312 | } 313 | 314 | #[test] 315 | fn ppustat_is_read_only_register() { 316 | assert_register_ignores_writes(0x2002, &|ref ppu| ppu.reg.ppustat.bits); 317 | assert_writing_register_fills_latch(0x2002); 318 | assert_register_is_readable(0x2002, &|ref mut ppu, val| { 319 | ppu.reg.ppustat = PPUStat::from_bits_truncate(val); 320 | ppu.reg.dyn_latch = val; 321 | }); 322 | } 323 | 324 | #[test] 325 | fn reading_ppustat_returns_part_of_dynlatch() { 326 | let mut ppu = create_test_ppu(); 327 | ppu.reg.dyn_latch = 0b0001_0101; 328 | ppu.reg.ppustat = PPUStat::from_bits_truncate(0b1010_0101); 329 | assert_eq!(ppu.read(0x2002), 0b1011_0101); 330 | } 331 | 332 | #[test] 333 | fn reading_ppustat_clears_addr_latch() { 334 | let mut ppu = create_test_ppu(); 335 | ppu.reg.address_latch = AddrByte::Low; 336 | ppu.read(0x2002); 337 | assert_eq!(ppu.reg.address_latch, AddrByte::High); 338 | } 339 | 340 | #[test] 341 | fn oamaddr_is_write_only_register() { 342 | assert_register_single_writable(0x2003, &|ref ppu| ppu.reg.oamaddr); 343 | assert_writing_register_fills_latch(0x2003); 344 | assert_register_not_readable(0x2003); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/ppu/background_rendering.rs: -------------------------------------------------------------------------------- 1 | use super::PaletteIndex; 2 | use super::SCREEN_BUFFER_SIZE; 3 | use super::SCREEN_HEIGHT; 4 | use super::SCREEN_WIDTH; 5 | use super::TilePattern; 6 | use super::ppu_memory::PPUMemory; 7 | use super::ppu_reg::PPUReg; 8 | use memory::MemSegment; 9 | use std::cmp; 10 | 11 | const TILES_PER_LINE: usize = 34; 12 | 13 | #[derive(Debug, Copy, Clone, PartialEq)] 14 | struct TileAttribute { 15 | bits: u8, 16 | } 17 | 18 | impl TileAttribute { 19 | fn new(bits: u8) -> TileAttribute { 20 | TileAttribute { bits: bits } 21 | } 22 | 23 | fn get_palette(&self, x: u16, y: u16) -> u8 { 24 | let y = y % SCREEN_HEIGHT as u16; 25 | let mut at = self.bits; 26 | if y & 0x10 != 0 { 27 | at >>= 4 28 | } 29 | if x & 0x10 != 0 { 30 | at >>= 2 31 | } 32 | at & 0x03 33 | } 34 | 35 | #[cfg(feature = "debug_features")] 36 | fn get_palette_mask(&self, x: u16, y: u16) -> u8 { 37 | let y = y % SCREEN_HEIGHT as u16; 38 | let mut at = 0xFF; 39 | if y & 0x10 != 0 { 40 | at &= 0b1111_0000; 41 | } else { 42 | at &= 0b0000_1111; 43 | } 44 | 45 | if x & 0x10 != 0 { 46 | at &= 0b1100_1100; 47 | } else { 48 | at &= 0b0011_0011; 49 | } 50 | at 51 | } 52 | } 53 | 54 | impl Default for TileAttribute { 55 | fn default() -> TileAttribute { 56 | TileAttribute { bits: 0 } 57 | } 58 | } 59 | 60 | pub struct BackgroundRenderer { 61 | idx: Box<[[u8; TILES_PER_LINE]; SCREEN_HEIGHT]>, 62 | tile: Box<[[TilePattern; TILES_PER_LINE]; SCREEN_HEIGHT]>, 63 | attr: Box<[[u8; TILES_PER_LINE]; SCREEN_HEIGHT]>, 64 | } 65 | 66 | fn draw_segment( 67 | pattern_line: &[TilePattern; TILES_PER_LINE], 68 | attr_line: &[u8; TILES_PER_LINE], 69 | pixel_line: &mut [PaletteIndex], 70 | fine_x_scroll: usize, 71 | start: usize, 72 | stop: usize, 73 | ) { 74 | for (pixel, item) in pixel_line.iter_mut().enumerate().take(stop).skip(start) { 75 | let displayed_pixel = pixel + fine_x_scroll; 76 | render_single_pixel(pattern_line, attr_line, displayed_pixel, item); 77 | } 78 | } 79 | 80 | fn render_single_pixel( 81 | pattern_line: &[TilePattern], 82 | attr_line: &[u8], 83 | displayed_pixel: usize, 84 | item: &mut PaletteIndex, 85 | ) { 86 | let tile_idx = displayed_pixel / 8; 87 | let pattern = pattern_line[tile_idx]; 88 | let fine_x = displayed_pixel as u32 & 0x07; 89 | let color_id = pattern.get_color_in_pattern(fine_x); 90 | 91 | let palette_id = attr_line[tile_idx]; 92 | 93 | *item = PaletteIndex::from_packed(color_id | palette_id); 94 | } 95 | 96 | impl BackgroundRenderer { 97 | pub fn render( 98 | &mut self, 99 | buffer: &mut [PaletteIndex; SCREEN_BUFFER_SIZE], 100 | start: usize, 101 | stop: usize, 102 | reg: &PPUReg, 103 | ) { 104 | let mut current_scanline = start / SCREEN_WIDTH; 105 | let mut last_scanline_boundary = current_scanline * SCREEN_WIDTH; 106 | let next_scanline = current_scanline + 1; 107 | let mut next_scanline_boundary = next_scanline * SCREEN_WIDTH; 108 | 109 | let mut current = start; 110 | let fine_x_scroll = reg.scroll_x_fine() as usize; 111 | while current < stop { 112 | let segment_start = current - last_scanline_boundary; 113 | let segment_end = cmp::min(next_scanline_boundary, stop) - last_scanline_boundary; 114 | 115 | let pattern_line = &self.tile[current_scanline]; 116 | let attr_line = &self.attr[current_scanline]; 117 | let pixel_line = &mut buffer[last_scanline_boundary..next_scanline_boundary]; 118 | 119 | draw_segment( 120 | pattern_line, 121 | attr_line, 122 | pixel_line, 123 | fine_x_scroll, 124 | segment_start, 125 | segment_end, 126 | ); 127 | current_scanline += 1; 128 | last_scanline_boundary = next_scanline_boundary; 129 | current = next_scanline_boundary; 130 | next_scanline_boundary += SCREEN_WIDTH; 131 | } 132 | } 133 | 134 | pub fn run_cycle(&mut self, cyc: u16, sl: i16, reg: &mut PPUReg, mem: &mut PPUMemory) { 135 | // Match to update vram addresses 136 | self.update_vram_address(cyc, sl, reg); 137 | // VRAM Accesses 138 | self.read_data(cyc, sl, reg, mem); 139 | } 140 | 141 | fn update_vram_address(&self, cyc: u16, sl: i16, reg: &mut PPUReg) { 142 | if sl < 240 { 143 | match cyc { 144 | 280 if sl == -1 => self.copy_vertical(reg), 145 | 256 => self.increment_y(reg), 146 | 257 => self.copy_horizontal(reg), 147 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 148 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 149 | 232 | 240 | 248 | 328 | 336 => self.increment_x(reg), 150 | _ => (), 151 | } 152 | } 153 | } 154 | 155 | fn read_data(&mut self, cyc: u16, sl: i16, reg: &mut PPUReg, mem: &mut PPUMemory) { 156 | if sl == -1 { 157 | match cyc { 158 | // Fetches for next scanline 159 | 321 | 329 => self.fetch_nametable((cyc - 320) / 8, (sl + 1) % 240, reg, mem), 160 | 323 | 331 => self.fetch_attribute((cyc - 320) / 8, (sl + 1) % 240, reg, mem), 161 | 325 | 333 => self.fetch_tile_pattern((cyc - 320) / 8, (sl + 1) % 240, reg, mem), 162 | 163 | // The two garbage nametable fetches at the end of every scanline 164 | 337 | 339 => self.garbage_nametable_fetch(reg, mem), 165 | _ => (), 166 | } 167 | } else if sl < 240 { 168 | match cyc { 169 | // Normal fetches 170 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 | 65 | 73 | 81 | 89 | 97 | 105 | 113 | 171 | 121 | 129 | 137 | 145 | 153 | 161 | 169 | 177 | 185 | 193 | 201 | 209 | 217 | 172 | 225 | 233 | 241 | 249 => self.fetch_nametable((cyc + 16) / 8, sl, reg, mem), 173 | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 | 67 | 75 | 83 | 91 | 99 | 107 | 115 | 174 | 123 | 131 | 139 | 147 | 155 | 163 | 171 | 179 | 187 | 195 | 203 | 211 | 219 | 175 | 227 | 235 | 243 | 251 => self.fetch_attribute((cyc + 16) / 8, sl, reg, mem), 176 | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 | 69 | 77 | 85 | 93 | 101 | 109 | 117 | 177 | 125 | 133 | 141 | 149 | 157 | 165 | 173 | 181 | 189 | 197 | 205 | 213 | 221 | 178 | 229 | 237 | 245 | 253 => self.fetch_tile_pattern((cyc + 16) / 8, sl, reg, mem), 179 | 180 | // Fetches for next scanline 181 | 321 | 329 => self.fetch_nametable((cyc - 320) / 8, (sl + 1) % 240, reg, mem), 182 | 323 | 331 => self.fetch_attribute((cyc - 320) / 8, (sl + 1) % 240, reg, mem), 183 | 325 | 333 => self.fetch_tile_pattern((cyc - 320) / 8, (sl + 1) % 240, reg, mem), 184 | 185 | // The two garbage nametable fetches at the end of every scanline 186 | 337 | 339 => self.garbage_nametable_fetch(reg, mem), 187 | _ => (), 188 | } 189 | } 190 | } 191 | 192 | fn copy_vertical(&self, reg: &mut PPUReg) { 193 | let vertical_mask = 0b_111_10_11111_00000; 194 | reg.v = (reg.v & !vertical_mask) | (reg.t & vertical_mask); 195 | } 196 | 197 | fn copy_horizontal(&self, reg: &mut PPUReg) { 198 | let horizontal_mask = 0b_000_01_00000_11111; 199 | reg.v = (reg.v & !horizontal_mask) | (reg.t & horizontal_mask); 200 | } 201 | 202 | fn increment_x(&self, reg: &mut PPUReg) { 203 | if (reg.v & 0x001F) == 31 { 204 | reg.v &= !0x001F; // clear coarse x 205 | reg.v ^= 0x0400; // Switch nametable 206 | } else { 207 | reg.v += 1; // increment coarse x 208 | } 209 | } 210 | 211 | fn increment_y(&self, reg: &mut PPUReg) { 212 | if (reg.v & 0x7000) != 0x7000 { 213 | reg.v += 0x1000; // Increment fine Y 214 | } else { 215 | reg.v &= !0x7000; // Clear fine Y 216 | let mut coarse_y = (reg.v & 0x03E0) >> 5; 217 | if coarse_y == 29 { 218 | coarse_y = 0; 219 | reg.v ^= 0x0800; // Switch vertical nametable 220 | } else if coarse_y == 31 { 221 | coarse_y = 0; // Clear coarse_y, but do not switch nametable 222 | } else { 223 | coarse_y += 1; 224 | } 225 | reg.v = (reg.v & !0x03E0) | (coarse_y << 5); // copy coarse_y back into V. 226 | } 227 | } 228 | 229 | fn fetch_nametable(&mut self, tile_x: u16, y: i16, reg: &PPUReg, mem: &mut PPUMemory) { 230 | let nametable_addr = 0x2000 | (reg.v & 0x0FFF); 231 | self.idx[y as usize][tile_x as usize] = mem.read(nametable_addr); 232 | } 233 | 234 | #[cfg_attr(rustfmt, rustfmt_skip)] 235 | fn fetch_attribute(&mut self, tile_x: u16, y: i16, reg: &PPUReg, mem: &mut PPUMemory) { 236 | let addr = 0x23C0 | (reg.v & 0x0C00) | ((reg.v >> 4) & 0x38) | ((reg.v >> 2) & 0x07); 237 | let tile_attribute = TileAttribute::new(mem.read(addr)); 238 | let palette_id = tile_attribute.get_palette( 239 | tile_x * 8 + reg.get_scroll_x(), 240 | y as u16 + reg.get_scroll_y()); 241 | self.attr[y as usize][tile_x as usize] = palette_id << 2; 242 | } 243 | 244 | fn fetch_tile_pattern(&mut self, tile_x: u16, y: i16, reg: &PPUReg, mem: &mut PPUMemory) { 245 | self.tile[y as usize][tile_x as usize] = mem.read_tile_pattern( 246 | self.idx[y as usize][tile_x as usize], 247 | reg.scroll_y_fine(), 248 | reg.ppuctrl.background_table(), 249 | ); 250 | } 251 | 252 | fn garbage_nametable_fetch(&mut self, reg: &PPUReg, mem: &mut PPUMemory) { 253 | let nametable_addr = 0x2000 | (reg.v & 0x0FFF); 254 | mem.read(nametable_addr); 255 | } 256 | 257 | #[cfg(feature = "debug_features")] 258 | pub fn mouse_pick(&self, reg: &PPUReg, px_x: i32, px_y: i32) { 259 | let scanline = px_y as usize; 260 | let tile = (px_x / 8) as usize; 261 | let tile_id = self.idx[scanline][tile]; 262 | let attr = TileAttribute::new(self.attr[scanline][tile]); 263 | let scrolled_x = px_x as u16 + reg.get_scroll_x() as u16; 264 | let scrolled_y = px_y as u16 + reg.get_scroll_y() as u16; 265 | let palette = attr.get_palette(scrolled_x, scrolled_y); 266 | let palette_mask = attr.get_palette_mask(scrolled_x, scrolled_y); 267 | println!( 268 | "{:03}/{:03}: Tile: {:03}, Attribute: {:08b} & {:08b}, Palette: {}", 269 | scrolled_x / 8, 270 | scrolled_y / 8, 271 | tile_id, 272 | attr.bits, 273 | palette_mask, 274 | palette 275 | ); 276 | } 277 | } 278 | 279 | impl Default for BackgroundRenderer { 280 | fn default() -> BackgroundRenderer { 281 | // Work around the 32-element array limitation 282 | let (idx, tiles, attrs) = unsafe { 283 | use std::ptr; 284 | use std::mem; 285 | 286 | let mut idx: [[u8; TILES_PER_LINE]; SCREEN_HEIGHT] = mem::uninitialized(); 287 | let mut tiles: [[TilePattern; TILES_PER_LINE]; SCREEN_HEIGHT] = mem::uninitialized(); 288 | let mut attrs: [[u8; TILES_PER_LINE]; SCREEN_HEIGHT] = mem::uninitialized(); 289 | 290 | for element in idx.iter_mut() { 291 | let idx_line: [u8; TILES_PER_LINE] = [0; TILES_PER_LINE]; 292 | ptr::write(element, idx_line); 293 | } 294 | for element in tiles.iter_mut() { 295 | let tile_line: [TilePattern; TILES_PER_LINE] = [Default::default(); TILES_PER_LINE]; 296 | ptr::write(element, tile_line); 297 | } 298 | for element in attrs.iter_mut() { 299 | let attr_line: [u8; TILES_PER_LINE] = [0; TILES_PER_LINE]; 300 | ptr::write(element, attr_line); 301 | } 302 | 303 | (idx, tiles, attrs) 304 | }; 305 | 306 | BackgroundRenderer { 307 | idx: Box::new(idx), 308 | tile: Box::new(tiles), 309 | attr: Box::new(attrs), 310 | } 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/cart/ines.rs: -------------------------------------------------------------------------------- 1 | use cart::*; 2 | use nom::{IResult, be_u8, ErrorKind}; 3 | use std::convert::Into; 4 | 5 | pub const PRG_ROM_PAGE_SIZE: usize = 16384; 6 | pub const CHR_ROM_PAGE_SIZE: usize = 8192; 7 | pub const PRG_RAM_PAGE_SIZE: usize = 8192; 8 | pub const TRAINER_LENGTH: usize = 512; 9 | 10 | quick_error! { 11 | #[derive(Debug, PartialEq)] 12 | pub enum RomError { 13 | DamagedHeader { 14 | description("ROM data had missing or damaged header.") 15 | } 16 | UnexpectedEndOfData { 17 | description("Unexpected end of data.") 18 | } 19 | Nes2NotSupported { 20 | description("NES 2.0 ROMs are not currently supported.") 21 | } 22 | } 23 | } 24 | 25 | bitflags! { 26 | struct Flags6: u8 { 27 | const VERTICAL = 0b0000_0001; 28 | const SRAM = 0b0000_0010; 29 | const TRAINER = 0b0000_0100; 30 | const FOUR_SCREEN = 0b0000_1000; 31 | } 32 | } 33 | 34 | impl Into for Flags6 { 35 | fn into(self) -> ScreenMode { 36 | if self.contains(FOUR_SCREEN) { 37 | ScreenMode::FourScreen 38 | } else if self.contains(VERTICAL) { 39 | ScreenMode::Vertical 40 | } else { 41 | ScreenMode::Horizontal 42 | } 43 | } 44 | } 45 | 46 | bitflags! { 47 | struct Flags7: u8 { 48 | const VS_UNISYSTEM = 0b0000_0001; 49 | const PLAYCHOICE_10 = 0b0000_0010; 50 | const NES_2 = 0b0000_1100; 51 | } 52 | } 53 | 54 | impl Into for Flags7 { 55 | fn into(self) -> System { 56 | if self.contains(VS_UNISYSTEM) { 57 | System::Vs 58 | } else if self.contains(PLAYCHOICE_10) { 59 | System::PC10 60 | } else { 61 | System::NES 62 | } 63 | } 64 | } 65 | 66 | bitflags! { 67 | struct Flags9: u8 { 68 | const PAL = 0b0000_0001; 69 | } 70 | } 71 | 72 | impl Into for Flags9 { 73 | fn into(self) -> TvFormat { 74 | if self.contains(PAL) { 75 | TvFormat::PAL 76 | } else { 77 | TvFormat::NTSC 78 | } 79 | } 80 | } 81 | 82 | pub struct Rom { 83 | mapper: u8, 84 | screen_mode: ScreenMode, 85 | sram: bool, 86 | system: System, 87 | tv_format: TvFormat, 88 | pub prg_rom: Vec, 89 | pub chr_rom: Vec, 90 | pub prg_ram_size: usize, 91 | } 92 | 93 | fn validate_not_nes2(input: &[u8], flags_7: Flags7) -> IResult<&[u8], ()> { 94 | if (flags_7.bits() & 0b0000_1100) == 0b0000_1000 { 95 | IResult::Error(error_code!(ErrorKind::Custom(1))) 96 | } else { 97 | IResult::Done(input, ()) 98 | } 99 | } 100 | 101 | fn parse_rom(input: &[u8]) -> IResult<&[u8], Rom> { 102 | do_parse!(input, 103 | tag!(b"NES\x1A") >> 104 | prg_pages: be_u8 >> 105 | chr_pages: be_u8 >> 106 | flags_6: bits!(tuple!( 107 | take_bits!(u8, 4), 108 | map_opt!(take_bits!(u8, 4), Flags6::from_bits))) >> 109 | flags_7: bits!(tuple!( 110 | take_bits!(u8, 4), 111 | map_opt!(take_bits!(u8, 4), Flags7::from_bits))) >> 112 | call!(validate_not_nes2, flags_7.1) >> 113 | prg_ram_pages: be_u8 >> 114 | flags_9: map_opt!(be_u8, Flags9::from_bits) >> 115 | tag!([0u8; 6]) >> 116 | //Skip the trainer if there is one 117 | cond!(flags_6.1.contains(TRAINER), take!(TRAINER_LENGTH)) >> 118 | prg_rom: take!(prg_pages as usize * PRG_ROM_PAGE_SIZE) >> 119 | chr_rom: take!(chr_pages as usize * CHR_ROM_PAGE_SIZE) >> 120 | ( Rom { 121 | mapper: (flags_7.0 << 4) | flags_6.0, 122 | screen_mode: flags_6.1.into(), 123 | sram: flags_6.1.contains(SRAM), 124 | system: flags_7.1.into(), 125 | tv_format: flags_9.into(), 126 | prg_rom: prg_rom.into(), 127 | chr_rom: chr_rom.into(), 128 | prg_ram_size: if prg_ram_pages == 0 { 129 | PRG_RAM_PAGE_SIZE 130 | } else { 131 | prg_ram_pages as usize * PRG_RAM_PAGE_SIZE 132 | }, 133 | } ) 134 | ) 135 | } 136 | 137 | impl Rom { 138 | /// Parse the given bytes as an iNES 1.0 header. 139 | /// NES 2.0 is not supported yet. 140 | pub fn parse(data: &[u8]) -> Result { 141 | match parse_rom(data) { 142 | IResult::Done(_, rom) => Ok(rom), 143 | IResult::Error(err) => { 144 | match err { 145 | ErrorKind::Custom(_) => Err(RomError::Nes2NotSupported), 146 | _ => Err(RomError::DamagedHeader), 147 | } 148 | } 149 | IResult::Incomplete(_) => Err(RomError::UnexpectedEndOfData), 150 | } 151 | } 152 | 153 | pub fn screen_mode(&self) -> ScreenMode { 154 | self.screen_mode 155 | } 156 | 157 | pub fn sram(&self) -> bool { 158 | self.sram 159 | } 160 | 161 | pub fn system(&self) -> System { 162 | self.system 163 | } 164 | 165 | pub fn tv_system(&self) -> TvFormat { 166 | self.tv_format 167 | } 168 | 169 | pub fn mapper(&self) -> u8 { 170 | self.mapper 171 | } 172 | } 173 | 174 | #[cfg(test)] 175 | mod tests { 176 | extern crate rand; 177 | use self::rand::{Rng, thread_rng}; 178 | use super::*; 179 | 180 | struct RomBuilder { 181 | header: Vec, 182 | trainer: Vec, 183 | prg_rom: Vec, 184 | chr_rom: Vec, 185 | } 186 | 187 | fn set_bit(byte: &mut u8, bit_num: u8) { 188 | *byte |= 1u8 << bit_num; 189 | } 190 | 191 | fn generate_bytes(size: usize) -> Vec { 192 | let mut rng = thread_rng(); 193 | let mut bytes: Vec = vec![0u8; size]; 194 | rng.fill_bytes(&mut bytes); 195 | bytes 196 | } 197 | 198 | impl RomBuilder { 199 | fn new() -> RomBuilder { 200 | let mut header = b"NES\x1A".to_vec(); 201 | header.extend([0; 12].iter().cloned()); 202 | 203 | RomBuilder { 204 | header: header, 205 | trainer: vec![], 206 | prg_rom: vec![], 207 | chr_rom: vec![], 208 | } 209 | } 210 | 211 | fn set_prg_page_count(&mut self, count: u8) { 212 | self.header[4] = count; 213 | self.prg_rom = generate_bytes(count as usize * PRG_ROM_PAGE_SIZE); 214 | } 215 | 216 | fn set_chr_page_count(&mut self, count: u8) { 217 | self.header[5] = count; 218 | self.chr_rom = generate_bytes(count as usize * CHR_ROM_PAGE_SIZE); 219 | } 220 | 221 | fn set_mirroring(&mut self) { 222 | set_bit(&mut self.header[6], 0) 223 | } 224 | 225 | fn set_sram(&mut self) { 226 | set_bit(&mut self.header[6], 1) 227 | } 228 | 229 | fn set_fourscreen(&mut self) { 230 | set_bit(&mut self.header[6], 3) 231 | } 232 | 233 | fn set_pc10(&mut self) { 234 | set_bit(&mut self.header[7], 1) 235 | } 236 | 237 | fn set_vs(&mut self) { 238 | set_bit(&mut self.header[7], 0) 239 | } 240 | 241 | fn set_mapper(&mut self, mapper: u8) { 242 | self.header[6] = (self.header[6] & 0x0F) | ((mapper & 0x0Fu8) << 4); 243 | self.header[7] = (self.header[7] & 0x0F) | ((mapper & 0xF0u8)); 244 | } 245 | 246 | fn set_prg_ram_pages(&mut self, pages: u8) { 247 | self.header[8] = pages; 248 | } 249 | 250 | fn set_nes2(&mut self) { 251 | self.header[7] = (self.header[7] & 0b00001100) | 0b0000_1000; 252 | } 253 | 254 | fn set_pal(&mut self) { 255 | set_bit(&mut self.header[9], 0) 256 | } 257 | 258 | fn build(&self) -> Vec { 259 | let mut buf = self.header.clone(); 260 | buf.extend(self.trainer.iter().clone()); 261 | buf.extend(self.prg_rom.iter().clone()); 262 | buf.extend(self.chr_rom.iter().clone()); 263 | buf 264 | } 265 | 266 | fn build_rom(&self) -> Rom { 267 | Rom::parse(&self.build()).unwrap() 268 | } 269 | } 270 | 271 | #[test] 272 | fn parse_returns_failure_on_empty_input() { 273 | assert!(Rom::parse(&Vec::new()).err().unwrap() == RomError::UnexpectedEndOfData); 274 | } 275 | 276 | #[test] 277 | fn parse_returns_failure_on_incorrect_magic_bytes() { 278 | let mut builder = RomBuilder::new(); 279 | builder.set_prg_page_count(3); 280 | let mut buf = builder.build(); 281 | buf[3] = b'0'; 282 | assert!(Rom::parse(&buf).err().unwrap() == RomError::DamagedHeader); 283 | } 284 | 285 | #[test] 286 | fn parse_returns_failure_on_partial_input() { 287 | let mut builder = RomBuilder::new(); 288 | builder.set_prg_page_count(3); 289 | let mut buf = builder.build(); 290 | buf.truncate(300); 291 | assert!(Rom::parse(&buf).err().unwrap() == RomError::UnexpectedEndOfData); 292 | } 293 | 294 | #[test] 295 | fn parse_returns_failure_on_damaged_input() { 296 | let mut buf = RomBuilder::new().build(); 297 | buf[2] = 155; 298 | assert!(Rom::parse(&buf).err().unwrap() == RomError::DamagedHeader); 299 | } 300 | 301 | #[test] 302 | fn parse_returns_failure_on_nes2_input() { 303 | let mut builder = RomBuilder::new(); 304 | builder.set_nes2(); 305 | let buf = builder.build(); 306 | assert!(Rom::parse(&buf).err().unwrap() == RomError::Nes2NotSupported); 307 | } 308 | 309 | #[test] 310 | fn test_prg_rom() { 311 | let mut builder = RomBuilder::new(); 312 | assert_eq!(&builder.build_rom().prg_rom, &vec![]); 313 | 314 | builder.set_prg_page_count(4); 315 | assert_eq!(&builder.build_rom().prg_rom, &builder.prg_rom); 316 | } 317 | 318 | #[test] 319 | fn test_chr_rom() { 320 | let mut builder = RomBuilder::new(); 321 | assert_eq!(&builder.build_rom().chr_rom, &vec![]); 322 | 323 | builder.set_chr_page_count(150); 324 | assert_eq!(&builder.build_rom().chr_rom, &builder.chr_rom); 325 | } 326 | 327 | #[test] 328 | fn test_screen_mode_without_fourscreen() { 329 | let mut builder = RomBuilder::new(); 330 | assert_eq!(builder.build_rom().screen_mode(), ScreenMode::Horizontal); 331 | 332 | builder.set_mirroring(); 333 | assert_eq!(builder.build_rom().screen_mode(), ScreenMode::Vertical); 334 | } 335 | 336 | #[test] 337 | fn test_screen_mode_with_fourscreen() { 338 | let mut builder = RomBuilder::new(); 339 | builder.set_fourscreen(); 340 | assert_eq!(builder.build_rom().screen_mode(), ScreenMode::FourScreen); 341 | 342 | builder.set_mirroring(); 343 | assert_eq!(builder.build_rom().screen_mode(), ScreenMode::FourScreen); 344 | } 345 | 346 | #[test] 347 | fn test_sram() { 348 | let mut builder = RomBuilder::new(); 349 | assert_eq!(builder.build_rom().sram(), false); 350 | 351 | builder.set_sram(); 352 | assert_eq!(builder.build_rom().sram(), true); 353 | } 354 | 355 | #[test] 356 | fn test_system() { 357 | let builder = RomBuilder::new(); 358 | assert_eq!(builder.build_rom().system(), System::NES); 359 | 360 | let mut builder = RomBuilder::new(); 361 | builder.set_vs(); 362 | assert_eq!(builder.build_rom().system(), System::Vs); 363 | 364 | let mut builder = RomBuilder::new(); 365 | builder.set_pc10(); 366 | assert_eq!(builder.build_rom().system(), System::PC10); 367 | } 368 | 369 | #[test] 370 | fn test_tv_system() { 371 | let mut builder = RomBuilder::new(); 372 | assert_eq!(builder.build_rom().tv_system(), TvFormat::NTSC); 373 | 374 | builder.set_pal(); 375 | assert_eq!(builder.build_rom().tv_system(), TvFormat::PAL); 376 | } 377 | 378 | #[test] 379 | fn test_mapper() { 380 | let mut builder = RomBuilder::new(); 381 | assert_eq!(builder.build_rom().mapper(), 0x00u8); 382 | 383 | builder.set_mapper(0x0Au8); 384 | assert_eq!(builder.build_rom().mapper(), 0x0Au8); 385 | 386 | builder.set_mapper(0xF0u8); 387 | assert_eq!(builder.build_rom().mapper(), 0xF0u8); 388 | } 389 | 390 | #[test] 391 | fn test_prg_ram_pages() { 392 | let mut builder = RomBuilder::new(); 393 | builder.set_prg_ram_pages(1); 394 | assert_eq!(builder.build_rom().prg_ram_size, PRG_RAM_PAGE_SIZE); 395 | 396 | builder.set_prg_ram_pages(0); 397 | assert_eq!(builder.build_rom().prg_ram_size, PRG_RAM_PAGE_SIZE); 398 | 399 | builder.set_prg_ram_pages(15); 400 | assert_eq!(builder.build_rom().prg_ram_size, 15 * PRG_RAM_PAGE_SIZE); 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/ppu/sprite_rendering.rs: -------------------------------------------------------------------------------- 1 | use super::PaletteIndex; 2 | use super::PaletteSet; 3 | use super::SCREEN_BUFFER_SIZE; 4 | use super::SCREEN_HEIGHT; 5 | use super::SCREEN_WIDTH; 6 | use super::TilePattern; 7 | use super::ppu_memory::PPUMemory; 8 | use super::ppu_reg::PPUReg; 9 | use memory::MemSegment; 10 | use std::cmp; 11 | 12 | bitflags! { 13 | struct OAMAttr : u8 { 14 | const FLIP_VERT = 0b1000_0000; 15 | const FLIP_HORZ = 0b0100_0000; 16 | const BEHIND = 0b0010_0000; 17 | #[allow(dead_code)] 18 | const PALETTE = 0b0000_0011; 19 | } 20 | } 21 | 22 | impl OAMAttr { 23 | fn palette(&self) -> u8 { 24 | self.bits & 0x03 25 | } 26 | 27 | fn priority(&self) -> bool { 28 | !self.contains(BEHIND) 29 | } 30 | } 31 | 32 | #[derive(Debug, Copy, Clone)] 33 | struct OAMEntry { 34 | y: u16, 35 | tile: u8, 36 | attr: OAMAttr, 37 | x: u8, 38 | } 39 | 40 | impl OAMEntry { 41 | fn is_on_scanline(&self, scanline: u16, sprite_height: u16) -> bool { 42 | self.y <= scanline && scanline < self.y + sprite_height 43 | } 44 | 45 | fn build_details( 46 | &self, 47 | idx: usize, 48 | sl: u16, 49 | reg: &PPUReg, 50 | mem: &mut PPUMemory, 51 | ) -> SpriteDetails { 52 | let tile_id = self.tile; 53 | let fine_y_scroll = get_fine_scroll( 54 | reg.ppuctrl.sprite_height(), 55 | sl, 56 | self.y, 57 | self.attr.contains(FLIP_VERT), 58 | ); 59 | let tile = if reg.ppuctrl.tall_sprites() { 60 | let tile_table = (tile_id as u16 & 0b0000_0001) << 12; 61 | let mut tile_id = tile_id & 0b1111_1110; 62 | let mut fine_y_scroll = fine_y_scroll; 63 | if fine_y_scroll >= 8 { 64 | tile_id += 1; 65 | fine_y_scroll -= 8; 66 | } 67 | mem.read_tile_pattern(tile_id, fine_y_scroll, tile_table) 68 | } else { 69 | let tile_table = reg.ppuctrl.sprite_table(); 70 | mem.read_tile_pattern(tile_id, fine_y_scroll, tile_table) 71 | }; 72 | SpriteDetails { 73 | idx: idx, 74 | x: self.x, 75 | attr: self.attr, 76 | tile: tile, 77 | } 78 | } 79 | } 80 | 81 | impl Default for OAMEntry { 82 | fn default() -> OAMEntry { 83 | OAMEntry { 84 | y: 0xFF, 85 | tile: 0, 86 | attr: OAMAttr::from_bits_truncate(0), 87 | x: 0xFF, 88 | } 89 | } 90 | } 91 | 92 | impl MemSegment for OAMEntry { 93 | fn read(&mut self, idx: u16) -> u8 { 94 | match idx { 95 | 0 => self.y as u8, 96 | 1 => self.tile, 97 | 2 => self.attr.bits(), 98 | 3 => self.x, 99 | x => invalid_address!(x), 100 | } 101 | } 102 | 103 | fn write(&mut self, idx: u16, val: u8) { 104 | match idx { 105 | 0 => self.y = val as u16, 106 | 1 => self.tile = val, 107 | 2 => self.attr = OAMAttr::from_bits_truncate(val), 108 | 3 => self.x = val, 109 | x => invalid_address!(x), 110 | } 111 | } 112 | } 113 | 114 | #[derive(Debug, Copy, Clone)] 115 | struct SpriteDetails { 116 | idx: usize, 117 | x: u8, 118 | attr: OAMAttr, 119 | tile: TilePattern, 120 | } 121 | const NO_SPRITE: SpriteDetails = SpriteDetails { 122 | idx: 0xFF, 123 | x: 0xFF, 124 | attr: OAMAttr { bits: 0 }, 125 | tile: ::ppu::NO_TILE, 126 | }; 127 | const EMPTY_SECONDARY_OAM_LINE: [SpriteDetails; 8] = [NO_SPRITE; 8]; 128 | 129 | impl SpriteDetails { 130 | fn do_get_pixel(&self, x: u16) -> PaletteIndex { 131 | let fine_x = get_fine_scroll(8, x, self.x as u16, self.attr.contains(FLIP_HORZ)); 132 | let attr = self.attr; 133 | let color_id = self.tile.get_color_in_pattern(fine_x as u32); 134 | PaletteIndex::from_unpacked(PaletteSet::Sprite, attr.palette(), color_id) 135 | } 136 | 137 | #[allow(needless_range_loop)] // false positive - we need x to get the pixels. 138 | fn hit_test( 139 | &self, 140 | original_pixel_line: &[PaletteIndex; SCREEN_WIDTH], 141 | reg: &mut PPUReg, 142 | intersection: &Interval, 143 | ) { 144 | for x in intersection.start..(intersection.end) { 145 | if !original_pixel_line[x].is_transparent() && 146 | !self.do_get_pixel(x as u16).is_transparent() 147 | { 148 | reg.ppustat.insert(::ppu::ppu_reg::SPRITE_0); 149 | return; 150 | } 151 | } 152 | } 153 | 154 | fn blit( 155 | &self, 156 | original_pixel_line: &[PaletteIndex; SCREEN_WIDTH], 157 | pixel_line: &mut [PaletteIndex], 158 | intersection: &Interval, 159 | ) { 160 | for x in intersection.start..(intersection.end) { 161 | pixel_line[x] = self.mix( 162 | original_pixel_line[x], 163 | pixel_line[x], 164 | self.do_get_pixel(x as u16), 165 | ) 166 | } 167 | } 168 | 169 | fn mix( 170 | &self, 171 | background: PaletteIndex, 172 | current: PaletteIndex, 173 | sprite: PaletteIndex, 174 | ) -> PaletteIndex { 175 | if !sprite.is_transparent() && !self.attr.priority() && !background.is_transparent() { 176 | background 177 | } else if current.is_transparent() || (self.attr.priority() && !sprite.is_transparent()) { 178 | sprite 179 | } else { 180 | current 181 | } 182 | } 183 | } 184 | 185 | impl Default for SpriteDetails { 186 | fn default() -> SpriteDetails { 187 | NO_SPRITE 188 | } 189 | } 190 | 191 | #[derive(Debug)] 192 | struct Interval { 193 | start: usize, 194 | end: usize, 195 | } 196 | 197 | impl Interval { 198 | fn new(start: usize, end: usize) -> Interval { 199 | Interval { 200 | start: start, 201 | end: end, 202 | } 203 | } 204 | 205 | fn intersects_with(&self, other: &Interval) -> bool { 206 | self.start < other.end && self.end > other.start 207 | } 208 | 209 | fn intersection(&self, other: &Interval) -> Interval { 210 | Interval { 211 | start: cmp::max(self.start, other.start), 212 | end: cmp::min(self.end, other.end), 213 | } 214 | } 215 | } 216 | 217 | pub struct SpriteRenderer { 218 | primary_oam: Box<[OAMEntry; 64]>, 219 | secondary_oam: Box<[[SpriteDetails; 8]; SCREEN_HEIGHT]>, 220 | } 221 | 222 | impl Default for SpriteRenderer { 223 | fn default() -> SpriteRenderer { 224 | SpriteRenderer { 225 | primary_oam: Box::new([Default::default(); 64]), 226 | secondary_oam: Box::new([[Default::default(); 8]; SCREEN_HEIGHT]), 227 | } 228 | } 229 | } 230 | 231 | fn get_fine_scroll(size: u16, screen_dist: u16, sprite_dist: u16, flip: bool) -> u16 { 232 | let scroll = screen_dist - sprite_dist; 233 | if flip { (size - 1) - scroll } else { scroll } 234 | } 235 | 236 | impl SpriteRenderer { 237 | pub fn render( 238 | &mut self, 239 | buffer: &mut [PaletteIndex; SCREEN_BUFFER_SIZE], 240 | reg: &mut PPUReg, 241 | start: usize, 242 | stop: usize, 243 | ) { 244 | self.draw(buffer, reg, start, stop) 245 | } 246 | 247 | pub fn sprite_eval(&mut self, scanline: u16, reg: &PPUReg, mem: &mut PPUMemory) { 248 | if scanline + 1 >= SCREEN_HEIGHT as u16 { 249 | return; 250 | } 251 | let mut n = 0; 252 | let sprite_height = reg.ppuctrl.sprite_height(); 253 | let secondary_oam_line = &mut self.secondary_oam[scanline as usize + 1]; 254 | secondary_oam_line.copy_from_slice(&EMPTY_SECONDARY_OAM_LINE); 255 | for x in 0..64 { 256 | let oam = &self.primary_oam[x]; 257 | if oam.is_on_scanline(scanline, sprite_height) { 258 | secondary_oam_line[n] = oam.build_details(x, scanline, reg, mem); 259 | n += 1; 260 | if n == 8 { 261 | return; 262 | } 263 | } 264 | } 265 | } 266 | 267 | fn draw( 268 | &mut self, 269 | buffer: &mut [PaletteIndex; SCREEN_BUFFER_SIZE], 270 | reg: &mut PPUReg, 271 | start: usize, 272 | stop: usize, 273 | ) { 274 | let mut current_scanline = start / SCREEN_WIDTH; 275 | let mut last_scanline_boundary = current_scanline * SCREEN_WIDTH; 276 | let next_scanline = current_scanline + 1; 277 | let mut next_scanline_boundary = next_scanline * SCREEN_WIDTH; 278 | 279 | let mut current = start; 280 | while current < stop { 281 | let segment_start = current - last_scanline_boundary; 282 | let segment_end = cmp::min(next_scanline_boundary, stop) - last_scanline_boundary; 283 | 284 | self.render_segment( 285 | buffer, 286 | reg, 287 | current_scanline, 288 | last_scanline_boundary, 289 | next_scanline_boundary, 290 | segment_start, 291 | segment_end, 292 | ); 293 | current_scanline += 1; 294 | last_scanline_boundary = next_scanline_boundary; 295 | current = next_scanline_boundary; 296 | next_scanline_boundary += SCREEN_WIDTH; 297 | } 298 | } 299 | 300 | #[allow(too_many_arguments)] 301 | fn render_segment( 302 | &mut self, 303 | buffer: &mut [PaletteIndex; SCREEN_BUFFER_SIZE], 304 | reg: &mut PPUReg, 305 | scanline: usize, 306 | line_start: usize, 307 | line_stop: usize, 308 | start: usize, 309 | stop: usize, 310 | ) { 311 | let oam_line = &self.secondary_oam[scanline]; 312 | let pixel_line = &mut buffer[line_start..line_stop]; 313 | 314 | let mut original_pixel_line: [PaletteIndex; SCREEN_WIDTH] = 315 | unsafe { ::std::mem::uninitialized() }; 316 | original_pixel_line.copy_from_slice(pixel_line); 317 | 318 | let segment = Interval::new(start, stop); 319 | 320 | for sprite in oam_line.iter().rev() { 321 | let sprite_interval = Interval::new(sprite.x as usize, sprite.x as usize + 8); 322 | if segment.intersects_with(&sprite_interval) { 323 | let intersection = segment.intersection(&sprite_interval); 324 | if sprite.idx == 0 { 325 | sprite.hit_test(&original_pixel_line, reg, &intersection); 326 | } 327 | sprite.blit(&original_pixel_line, pixel_line, &intersection); 328 | } 329 | } 330 | } 331 | 332 | #[cfg(feature = "debug_features")] 333 | pub fn mouse_pick(&self, px_x: i32, px_y: i32) { 334 | let scanline = px_y as usize; 335 | let pixel = px_x as u16; 336 | for sprite in &self.secondary_oam[scanline] { 337 | let spr_x = sprite.x as u16; 338 | if spr_x <= pixel && pixel <= (spr_x + 8) { 339 | println!("{:?}", sprite); 340 | } 341 | } 342 | } 343 | } 344 | 345 | /// Reads the primary OAM table. 346 | impl MemSegment for SpriteRenderer { 347 | fn read(&mut self, idx: u16) -> u8 { 348 | if idx > 256 { 349 | invalid_address!(idx); 350 | } 351 | self.primary_oam[idx as usize / 4].read(idx % 4) 352 | } 353 | 354 | fn write(&mut self, idx: u16, val: u8) { 355 | if idx > 256 { 356 | invalid_address!(idx); 357 | } 358 | self.primary_oam[idx as usize / 4].write(idx % 4, val) 359 | } 360 | } 361 | 362 | #[cfg(tests)] 363 | mod tests { 364 | use super::*; 365 | use memory::MemSegment; 366 | use ppu::PPU; 367 | 368 | #[test] 369 | fn reading_oamdata_uses_oamaddr_as_index_into_oam() { 370 | let mut ppu = create_test_ppu(); 371 | for x in 0u8..63u8 { 372 | ppu.oam[x as usize] = OAMEntry::new(x, x, x, x); 373 | } 374 | ppu.reg.oamaddr = 0; 375 | assert_eq!(ppu.read(0x2004), 0); 376 | ppu.reg.oamaddr = 10; 377 | assert_eq!(ppu.read(0x2004), 2); 378 | } 379 | 380 | #[test] 381 | fn writing_oamdata_uses_oamaddr_as_index_into_oam() { 382 | let mut ppu = create_test_ppu(); 383 | ppu.reg.oamaddr = 0; 384 | ppu.write(0x2004, 12); 385 | assert_eq!(ppu.oam[0].y, 12); 386 | ppu.reg.oamaddr = 10; 387 | ppu.write(0x2004, 3); 388 | assert_eq!(ppu.oam[2].attr.bits(), 3); 389 | } 390 | 391 | #[test] 392 | fn test_sprite_on_scanline() { 393 | let mut ppu = create_test_ppu(); 394 | let mut oam: OAMEntry = Default::default(); 395 | oam.y = 10; 396 | 397 | assert!(!ppu.is_on_scanline(oam, 9)); 398 | for sl in 10..18 { 399 | assert!(ppu.is_on_scanline(oam, sl)); 400 | } 401 | assert!(!ppu.is_on_scanline(oam, 18)); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "corrosion" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "blip_buf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "dynasm 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "dynasmrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "memmap 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "nom 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 13 | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "sdl2 0.29.1 (registry+https://github.com/rust-lang/crates.io-index)", 15 | "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 16 | "simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 17 | ] 18 | 19 | [[package]] 20 | name = "bitflags" 21 | version = "0.7.0" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | 24 | [[package]] 25 | name = "bitflags" 26 | version = "0.9.1" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | 29 | [[package]] 30 | name = "blip_buf" 31 | version = "0.1.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | dependencies = [ 34 | "blip_buf-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 35 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "blip_buf-sys" 40 | version = "0.1.3" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | dependencies = [ 43 | "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 45 | ] 46 | 47 | [[package]] 48 | name = "compiler_error" 49 | version = "0.1.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | 52 | [[package]] 53 | name = "dynasm" 54 | version = "0.1.2" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | dependencies = [ 57 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "dynasmrt" 64 | version = "0.1.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "memmap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 68 | ] 69 | 70 | [[package]] 71 | name = "fnv" 72 | version = "1.0.5" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | 75 | [[package]] 76 | name = "fs2" 77 | version = "0.2.5" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | dependencies = [ 80 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 83 | ] 84 | 85 | [[package]] 86 | name = "fs2" 87 | version = "0.4.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | dependencies = [ 90 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 93 | ] 94 | 95 | [[package]] 96 | name = "gcc" 97 | version = "0.3.51" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | 100 | [[package]] 101 | name = "kernel32-sys" 102 | version = "0.2.2" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | dependencies = [ 105 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "lazy_static" 111 | version = "0.2.8" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | 114 | [[package]] 115 | name = "libc" 116 | version = "0.2.28" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | 119 | [[package]] 120 | name = "memchr" 121 | version = "1.0.1" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | dependencies = [ 124 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "memmap" 129 | version = "0.4.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | dependencies = [ 132 | "fs2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 136 | ] 137 | 138 | [[package]] 139 | name = "memmap" 140 | version = "0.5.2" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | dependencies = [ 143 | "fs2 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 146 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 147 | ] 148 | 149 | [[package]] 150 | name = "nom" 151 | version = "3.2.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | dependencies = [ 154 | "compiler_error 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 156 | ] 157 | 158 | [[package]] 159 | name = "num" 160 | version = "0.1.40" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | dependencies = [ 163 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 166 | ] 167 | 168 | [[package]] 169 | name = "num-integer" 170 | version = "0.1.35" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | dependencies = [ 173 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 174 | ] 175 | 176 | [[package]] 177 | name = "num-iter" 178 | version = "0.1.34" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | dependencies = [ 181 | "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 183 | ] 184 | 185 | [[package]] 186 | name = "num-traits" 187 | version = "0.1.40" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | 190 | [[package]] 191 | name = "owning_ref" 192 | version = "0.3.3" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | dependencies = [ 195 | "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 196 | ] 197 | 198 | [[package]] 199 | name = "quick-error" 200 | version = "1.2.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | 203 | [[package]] 204 | name = "rand" 205 | version = "0.3.15" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | dependencies = [ 208 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 209 | ] 210 | 211 | [[package]] 212 | name = "sdl2" 213 | version = "0.29.1" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | dependencies = [ 216 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 217 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 218 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 219 | "num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 220 | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 221 | "sdl2-sys 0.27.3 (registry+https://github.com/rust-lang/crates.io-index)", 222 | ] 223 | 224 | [[package]] 225 | name = "sdl2-sys" 226 | version = "0.27.3" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | dependencies = [ 229 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 230 | ] 231 | 232 | [[package]] 233 | name = "sha1" 234 | version = "0.2.0" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | 237 | [[package]] 238 | name = "simd" 239 | version = "0.2.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | 242 | [[package]] 243 | name = "stable_deref_trait" 244 | version = "1.0.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | 247 | [[package]] 248 | name = "winapi" 249 | version = "0.2.8" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | 252 | [[package]] 253 | name = "winapi-build" 254 | version = "0.1.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | 257 | [metadata] 258 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 259 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 260 | "checksum blip_buf 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "45c136cf50e8d0c47fe415008a6be8b50a9a56a6741fffbe2e580e007eda417d" 261 | "checksum blip_buf-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7aae962adb2875cbf7e172905370b7c3ad26b96e970eee1be0ac2ea2491bcf23" 262 | "checksum compiler_error 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ef975f6d3622b15ea669c8075fefa5a9bc4bfae1b8b221ab9962daff003b3047" 263 | "checksum dynasm 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a43da805488fb79bf92d0a691c2f87b82f6e62b3d42895e593353b1c1fbec31f" 264 | "checksum dynasmrt 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "095f5f6a53ba822966de6379c162f99cfd8b05a56baf8ffb1c66333fcdbd154a" 265 | "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" 266 | "checksum fs2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bcd414e5a1a979b931bb92f41b7a54106d3f6d2e6c253e9ce943b7cd468251ef" 267 | "checksum fs2 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ab76cfd2aaa59b7bf6688ad9ba15bbae64bff97f04ea02144cfd3443e5c2866" 268 | "checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a" 269 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 270 | "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" 271 | "checksum libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb7b49972ee23d8aa1026c365a5b440ba08e35075f18c459980c7395c221ec48" 272 | "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" 273 | "checksum memmap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "69253224aa10070855ea8fe9dbe94a03fc2b1d7930bb340c9e586a7513716fea" 274 | "checksum memmap 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46f3c7359028b31999287dae4e5047ddfe90a23b7dca2282ce759b491080c99b" 275 | "checksum nom 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06989cbd367e06f787a451f3bc67d8c3e0eaa10b461cc01152ffab24261a31b1" 276 | "checksum num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "a311b77ebdc5dd4cf6449d81e4135d9f0e3b153839ac90e648a8ef538f923525" 277 | "checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" 278 | "checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" 279 | "checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" 280 | "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" 281 | "checksum quick-error 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c36987d4978eb1be2e422b1e0423a557923a5c3e7e6f31d5699e9aafaefa469" 282 | "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" 283 | "checksum sdl2 0.29.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c366cfa1f22d001774214ce2fb13f369af760b016bc79cc62d7f5ae15c00fea" 284 | "checksum sdl2-sys 0.27.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8d9f87e3d948f94f2d8688970422f49249c20e97f8f3aad76cb8729901d4eb10" 285 | "checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c" 286 | "checksum simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a94d14a2ae1f1f110937de5fb69e494372560181c7e1739a097fcc2cee37ba0" 287 | "checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b" 288 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 289 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 290 | -------------------------------------------------------------------------------- /src/cpu/nes_analyst.rs: -------------------------------------------------------------------------------- 1 | use cpu::CPU; 2 | use fnv::FnvHashMap; 3 | use fnv::FnvHashSet; 4 | use memory::MemSegment; 5 | 6 | pub struct Analyst<'a> { 7 | entry_point: u16, 8 | pc: u16, 9 | current_instruction: u16, 10 | cpu: &'a mut CPU, 11 | furthest_branch: u16, 12 | found_exit_point: bool, 13 | 14 | pub last_sign_flag_set: u16, 15 | pub last_overflow_flag_set: u16, 16 | pub last_zero_flag_set: u16, 17 | pub last_carry_flag_set: u16, 18 | 19 | instructions: FnvHashMap, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct InstructionAnalysis { 24 | pub is_branch_target: bool, 25 | 26 | pub sign_flag_used: bool, 27 | pub overflow_flag_used: bool, 28 | pub zero_flag_used: bool, 29 | pub carry_flag_used: bool, 30 | } 31 | 32 | impl Default for InstructionAnalysis { 33 | fn default() -> InstructionAnalysis { 34 | InstructionAnalysis { 35 | is_branch_target: false, 36 | 37 | sign_flag_used: false, 38 | overflow_flag_used: false, 39 | zero_flag_used: false, 40 | carry_flag_used: false, 41 | } 42 | } 43 | } 44 | 45 | #[derive(Debug)] 46 | pub struct BlockAnalysis { 47 | pub entry_point: u16, 48 | pub exit_point: u16, 49 | 50 | pub instructions: FnvHashMap, 51 | } 52 | 53 | impl<'a> Analyst<'a> { 54 | pub fn new(cpu: &'a mut CPU) -> Analyst<'a> { 55 | Analyst { 56 | entry_point: 0, 57 | pc: 0, 58 | current_instruction: 0, 59 | cpu: cpu, 60 | furthest_branch: 0, 61 | found_exit_point: false, 62 | 63 | last_sign_flag_set: 0, 64 | last_carry_flag_set: 0, 65 | last_zero_flag_set: 0, 66 | last_overflow_flag_set: 0, 67 | 68 | instructions: FnvHashMap::default(), 69 | } 70 | } 71 | 72 | #[cfg(feature = "debug_features")] 73 | pub fn find_exit_point(self, entry_point: u16) -> u16 { 74 | self.analyze(entry_point).exit_point 75 | } 76 | 77 | pub fn analyze(mut self, entry_point: u16) -> BlockAnalysis { 78 | self.entry_point = entry_point; 79 | self.pc = entry_point; 80 | 81 | let mut valid_instr_addrs: FnvHashSet = FnvHashSet::default(); 82 | 83 | while !self.found_exit_point { 84 | // Ensure that every instruction has an entry 85 | let temp_pc = self.pc; 86 | self.current_instruction = temp_pc; 87 | valid_instr_addrs.insert(temp_pc); 88 | self.get_instr_analysis(temp_pc); 89 | 90 | let opcode = self.read_incr_pc(); 91 | decode_opcode!(opcode, self); 92 | } 93 | 94 | self.remove_invalid_instructions(valid_instr_addrs); 95 | 96 | BlockAnalysis { 97 | entry_point: entry_point, 98 | exit_point: self.pc - 1, 99 | instructions: self.instructions, 100 | } 101 | } 102 | 103 | fn remove_invalid_instructions(&mut self, valid_instr_addrs: FnvHashSet) { 104 | let mut invalid_instrs: FnvHashSet = FnvHashSet::default(); 105 | 106 | { 107 | for addr in self.instructions.keys().cloned() { 108 | if !valid_instr_addrs.contains(&addr) { 109 | invalid_instrs.insert(addr); 110 | } 111 | } 112 | } 113 | 114 | for addr in &invalid_instrs { 115 | self.instructions.remove(addr); 116 | } 117 | } 118 | 119 | // Addressing modes 120 | fn immediate(&mut self) -> u8 { 121 | self.read_incr_pc(); 122 | 0 123 | } 124 | fn absolute(&mut self) -> u8 { 125 | self.read_w_incr_pc(); 126 | 0 127 | } 128 | fn absolute_x(&mut self) -> u8 { 129 | self.read_w_incr_pc(); 130 | 0 131 | } 132 | fn absolute_y(&mut self) -> u8 { 133 | self.read_w_incr_pc(); 134 | 0 135 | } 136 | fn zero_page(&mut self) -> u8 { 137 | self.read_incr_pc(); 138 | 0 139 | } 140 | fn zero_page_x(&mut self) -> u8 { 141 | self.read_incr_pc(); 142 | 0 143 | } 144 | fn zero_page_y(&mut self) -> u8 { 145 | self.read_incr_pc(); 146 | 0 147 | } 148 | fn indirect_x(&mut self) -> u8 { 149 | self.read_incr_pc(); 150 | 0 151 | } 152 | fn indirect_y(&mut self) -> u8 { 153 | self.read_incr_pc(); 154 | 0 155 | } 156 | fn accumulator(&mut self) -> u8 { 157 | 0 158 | } 159 | 160 | // Instructions 161 | // Stores 162 | fn stx(&mut self, _: u8) {} 163 | fn sty(&mut self, _: u8) {} 164 | fn sta(&mut self, _: u8) {} 165 | 166 | // Loads 167 | fn ldx(&mut self, _: u8) { 168 | self.sign_set(); 169 | self.zero_set(); 170 | } 171 | fn lda(&mut self, _: u8) { 172 | self.sign_set(); 173 | self.zero_set(); 174 | } 175 | fn ldy(&mut self, _: u8) { 176 | self.sign_set(); 177 | self.zero_set(); 178 | } 179 | 180 | // Logic/Math Ops 181 | fn bit(&mut self, _: u8) { 182 | self.sign_set(); 183 | self.overflow_set(); 184 | self.zero_set(); 185 | } 186 | fn and(&mut self, _: u8) { 187 | self.sign_set(); 188 | self.zero_set(); 189 | } 190 | fn ora(&mut self, _: u8) { 191 | self.sign_set(); 192 | self.zero_set(); 193 | } 194 | fn eor(&mut self, _: u8) { 195 | self.sign_set(); 196 | self.zero_set(); 197 | } 198 | fn adc(&mut self, _: u8) { 199 | self.carry_used(); 200 | self.carry_set(); 201 | self.sign_set(); 202 | self.zero_set(); 203 | self.overflow_set(); 204 | } 205 | fn sbc(&mut self, _: u8) { 206 | self.carry_used(); 207 | self.carry_set(); 208 | self.sign_set(); 209 | self.zero_set(); 210 | self.overflow_set(); 211 | } 212 | fn cmp(&mut self, _: u8) { 213 | self.zero_set(); 214 | self.sign_set(); 215 | self.carry_set(); 216 | } 217 | fn cpx(&mut self, _: u8) { 218 | self.zero_set(); 219 | self.sign_set(); 220 | self.carry_set(); 221 | } 222 | fn cpy(&mut self, _: u8) { 223 | self.zero_set(); 224 | self.sign_set(); 225 | self.carry_set(); 226 | } 227 | fn inc(&mut self, _: u8) { 228 | self.zero_set(); 229 | self.sign_set(); 230 | } 231 | fn iny(&mut self) { 232 | self.zero_set(); 233 | self.sign_set(); 234 | } 235 | fn inx(&mut self) { 236 | self.zero_set(); 237 | self.sign_set(); 238 | } 239 | fn dec(&mut self, _: u8) { 240 | self.zero_set(); 241 | self.sign_set(); 242 | } 243 | fn dey(&mut self) { 244 | self.zero_set(); 245 | self.sign_set(); 246 | } 247 | fn dex(&mut self) { 248 | self.zero_set(); 249 | self.sign_set(); 250 | } 251 | fn lsr(&mut self, _: u8) { 252 | self.zero_set(); 253 | self.sign_set(); 254 | self.carry_set(); 255 | } 256 | fn asl(&mut self, _: u8) { 257 | self.zero_set(); 258 | self.sign_set(); 259 | self.carry_set(); 260 | } 261 | fn ror(&mut self, _: u8) { 262 | self.carry_used(); 263 | self.carry_set(); 264 | self.sign_set(); 265 | self.zero_set(); 266 | } 267 | fn rol(&mut self, _: u8) { 268 | self.carry_used(); 269 | self.carry_set(); 270 | self.sign_set(); 271 | self.zero_set(); 272 | } 273 | 274 | // Jumps 275 | fn jmp(&mut self) { 276 | self.all_used(); 277 | self.read_w_incr_pc(); 278 | self.end_function(); 279 | } 280 | fn jmpi(&mut self) { 281 | self.all_used(); 282 | self.read_w_incr_pc(); 283 | self.end_function(); 284 | } 285 | fn jsr(&mut self) { 286 | self.all_used(); 287 | self.read_w_incr_pc(); 288 | self.end_function(); 289 | } 290 | fn rts(&mut self) { 291 | self.all_used(); 292 | self.end_function(); 293 | } 294 | fn rti(&mut self) { 295 | self.all_used(); 296 | self.end_function(); 297 | } 298 | fn brk(&mut self) { 299 | self.all_used(); 300 | self.end_function(); 301 | } 302 | 303 | fn unofficial(&self) {} 304 | 305 | fn end_function(&mut self) { 306 | if self.pc > self.furthest_branch { 307 | self.found_exit_point = true; 308 | } 309 | } 310 | 311 | // Branches 312 | fn bcs(&mut self) { 313 | self.all_used(); 314 | self.branch() 315 | } 316 | fn bcc(&mut self) { 317 | self.all_used(); 318 | self.branch() 319 | } 320 | fn beq(&mut self) { 321 | self.all_used(); 322 | self.branch() 323 | } 324 | fn bne(&mut self) { 325 | self.all_used(); 326 | self.branch() 327 | } 328 | fn bvs(&mut self) { 329 | self.all_used(); 330 | self.branch() 331 | } 332 | fn bvc(&mut self) { 333 | self.all_used(); 334 | self.branch() 335 | } 336 | fn bmi(&mut self) { 337 | self.all_used(); 338 | self.branch() 339 | } 340 | fn bpl(&mut self) { 341 | self.all_used(); 342 | self.branch() 343 | } 344 | 345 | fn branch(&mut self) { 346 | let arg = self.read_incr_pc(); 347 | let target = self.relative_addr(arg); 348 | self.get_instr_analysis(target).is_branch_target = true; 349 | if target > self.furthest_branch { 350 | self.furthest_branch = target; 351 | } 352 | } 353 | 354 | // Stack 355 | fn plp(&mut self) { 356 | self.carry_set(); 357 | self.sign_set(); 358 | self.zero_set(); 359 | self.overflow_set(); 360 | } 361 | fn php(&mut self) { 362 | self.all_used() 363 | } 364 | fn pla(&mut self) { 365 | self.sign_set(); 366 | self.zero_set(); 367 | } 368 | fn pha(&mut self) {} 369 | 370 | // Misc 371 | fn nop(&mut self) {} 372 | fn sec(&mut self) { 373 | self.carry_set(); 374 | } 375 | fn clc(&mut self) { 376 | self.carry_set(); 377 | } 378 | fn sei(&mut self) {} 379 | fn sed(&mut self) {} 380 | fn cld(&mut self) {} 381 | fn clv(&mut self) { 382 | self.overflow_set(); 383 | } 384 | fn tax(&mut self) { 385 | self.sign_set(); 386 | self.zero_set(); 387 | } 388 | fn tay(&mut self) { 389 | self.sign_set(); 390 | self.zero_set(); 391 | } 392 | fn tsx(&mut self) { 393 | self.sign_set(); 394 | self.zero_set(); 395 | } 396 | fn txa(&mut self) { 397 | self.sign_set(); 398 | self.zero_set(); 399 | } 400 | fn txs(&mut self) {} 401 | fn tya(&mut self) { 402 | self.sign_set(); 403 | self.zero_set(); 404 | } 405 | fn cli(&mut self) {} 406 | 407 | // Unofficial instructions 408 | fn u_nop(&mut self, _: u8) {} 409 | fn lax(&mut self, _: u8) { 410 | self.lda(0); 411 | self.ldx(0); 412 | } 413 | fn sax(&mut self, _: u8) {} 414 | fn dcp(&mut self, _: u8) { 415 | self.dec(0); 416 | self.cmp(0); 417 | } 418 | fn isc(&mut self, _: u8) { 419 | self.sbc(0); 420 | self.inc(0); 421 | } 422 | fn slo(&mut self, _: u8) { 423 | self.asl(0); 424 | self.ora(0); 425 | } 426 | fn rla(&mut self, _: u8) { 427 | self.rol(0); 428 | self.and(0); 429 | } 430 | fn sre(&mut self, _: u8) { 431 | self.lsr(0); 432 | self.eor(0); 433 | } 434 | fn rra(&mut self, _: u8) { 435 | self.adc(0); 436 | self.ror(0); 437 | } 438 | fn kil(&mut self) { 439 | self.end_function(); 440 | } 441 | fn unsupported(&mut self, _: u8) { 442 | self.end_function(); 443 | } 444 | 445 | fn relative_addr(&self, disp: u8) -> u16 { 446 | let disp = (disp as i8) as i16; // We want to sign-extend here. 447 | let pc = self.pc as i16; 448 | pc.wrapping_add(disp) as u16 449 | } 450 | 451 | fn read_incr_pc(&mut self) -> u8 { 452 | let pc = self.pc; 453 | let val: u8 = self.read_safe(pc); 454 | self.pc = self.pc.wrapping_add(1); 455 | val 456 | } 457 | 458 | fn get_instr_analysis(&mut self, addr: u16) -> &mut InstructionAnalysis { 459 | let default: InstructionAnalysis = Default::default(); 460 | self.instructions.entry(addr).or_insert(default) 461 | } 462 | 463 | fn read_w_incr_pc(&mut self) -> u16 { 464 | self.read_incr_pc() as u16 | ((self.read_incr_pc() as u16) << 8) 465 | } 466 | 467 | fn read_safe(&mut self, idx: u16) -> u8 { 468 | match idx { 469 | 0x2000...0x3FFF => 0xFF, 470 | 0x4000...0x401F => 0xFF, 471 | _ => self.cpu.read(idx), 472 | } 473 | } 474 | 475 | fn sign_set(&mut self) { 476 | self.last_sign_flag_set = self.current_instruction; 477 | } 478 | 479 | fn overflow_set(&mut self) { 480 | self.last_overflow_flag_set = self.current_instruction; 481 | } 482 | 483 | fn zero_set(&mut self) { 484 | self.last_zero_flag_set = self.current_instruction; 485 | } 486 | 487 | fn carry_set(&mut self) { 488 | self.last_carry_flag_set = self.current_instruction; 489 | } 490 | 491 | fn all_used(&mut self) { 492 | self.sign_used(); 493 | self.overflow_used(); 494 | self.zero_used(); 495 | self.carry_used(); 496 | } 497 | 498 | fn sign_used(&mut self) { 499 | if self.last_sign_flag_set == 0 { 500 | return; 501 | } 502 | self.instructions 503 | .get_mut(&self.last_sign_flag_set) 504 | .unwrap() 505 | .sign_flag_used = true; 506 | } 507 | 508 | fn overflow_used(&mut self) { 509 | if self.last_overflow_flag_set == 0 { 510 | return; 511 | } 512 | self.instructions 513 | .get_mut(&self.last_overflow_flag_set) 514 | .unwrap() 515 | .overflow_flag_used = true; 516 | } 517 | 518 | fn zero_used(&mut self) { 519 | if self.last_zero_flag_set == 0 { 520 | return; 521 | } 522 | self.instructions 523 | .get_mut(&self.last_zero_flag_set) 524 | .unwrap() 525 | .zero_flag_used = true; 526 | } 527 | 528 | fn carry_used(&mut self) { 529 | if self.last_carry_flag_set == 0 { 530 | return; 531 | } 532 | self.instructions 533 | .get_mut(&self.last_carry_flag_set) 534 | .unwrap() 535 | .carry_flag_used = true; 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /src/cpu/x86_64_compiler/addressing_modes.rs: -------------------------------------------------------------------------------- 1 | #![allow(private_in_public)] 2 | 3 | use super::Compiler; 4 | use cpu::CPU; 5 | use cpu::Registers; 6 | use dynasmrt::{DynasmApi, DynasmLabelApi}; 7 | use memory::MemSegment; 8 | 9 | pub extern "win64" fn read_memory(cpu: *mut CPU, addr: u16) -> u8 { 10 | unsafe { (*cpu).read(addr) } 11 | } 12 | 13 | // Expects the 6502 address in rcx and returns the byte in r8 (arg) 14 | macro_rules! call_read { 15 | ($this:ident) => {dynasm!($this.asm 16 | ; push rdx 17 | ; push rcx 18 | ;; store_registers!($this) 19 | ; pop rdx // Move the 6502 address to the second argument register 20 | ; mov rax, QWORD ::cpu::x86_64_compiler::addressing_modes::read_memory as _ 21 | ; mov rcx, rbx //Pointer to CPU is first arg 22 | ; sub rsp, 0x30 23 | ; call rax 24 | ; add rsp, 0x30 25 | ;; load_registers!($this) 26 | ; mov r8, rax //rax contains returned value, move it to r8 (which is arg) 27 | ; pop rdx 28 | );}; 29 | } 30 | 31 | // Optimized version of call_write that checks if the address is in RAM and if 32 | // so does the 33 | // write directly. Useful when this check can't be performed statically. 34 | macro_rules! fast_read { 35 | ($this:ident) => {dynasm!($this.asm 36 | ; cmp cx, WORD 0x1FFF 37 | ; ja >slow_read 38 | ; and rcx, DWORD 0x07FF 39 | ; mov arg, [ram + rcx] 40 | ; jmp >next 41 | ; slow_read: 42 | ;; call_read!($this) 43 | ; next: 44 | );}; 45 | } 46 | 47 | pub extern "win64" fn write_memory(cpu: *mut CPU, addr: u16, val: u8) { 48 | unsafe { (*cpu).write(addr, val) } 49 | } 50 | 51 | 52 | // Expects the 6502 address in rcx and the value in r8 (arg) 53 | macro_rules! call_write { 54 | ($this:ident) => {dynasm!($this.asm 55 | ; push rdx 56 | ; push r8 57 | ; push rcx 58 | ;; store_registers!($this) 59 | ; pop rdx // Move the 6502 address to the second argument register 60 | ; pop r8 61 | ; mov rax, QWORD ::cpu::x86_64_compiler::addressing_modes::write_memory as _ 62 | ; mov rcx, rbx //Pointer to CPU is first arg 63 | //Conveniently, we already have the value in r8 64 | ; sub rsp, 0x30 65 | ; call rax 66 | ; add rsp, 0x30 67 | ;; load_registers!($this) 68 | ; pop rdx 69 | );}; 70 | } 71 | 72 | // Optimized version of call_write that checks if the address is in RAM and if 73 | // so does the 74 | // write directly. Useful when this check can't be performed statically. 75 | macro_rules! fast_write { 76 | ($this:ident) => {dynasm!($this.asm 77 | ; cmp cx, WORD 0x1FFF 78 | ; ja >slow_write 79 | ; and rcx, DWORD 0x07FF 80 | ; mov [ram + rcx], arg 81 | ; jmp >next 82 | ; slow_write: 83 | ;; call_write!($this) 84 | ; next: 85 | );}; 86 | } 87 | pub trait AddressingMode: Copy { 88 | fn read_to_arg(&self, comp: &mut Compiler, tick_cycle: bool); 89 | fn write_from_arg(&self, comp: &mut Compiler); 90 | } 91 | 92 | #[derive(Debug, Copy, Clone)] 93 | pub struct ImmediateAddressingMode; 94 | impl AddressingMode for ImmediateAddressingMode { 95 | fn read_to_arg(&self, comp: &mut Compiler, _: bool) { 96 | let imm_arg = comp.read_incr_pc() as i8; 97 | dynasm!{comp.asm 98 | ; mov arg, BYTE imm_arg 99 | } 100 | } 101 | fn write_from_arg(&self, _: &mut Compiler) { 102 | panic!("Tried to write to an immediate address.") 103 | } 104 | } 105 | 106 | #[derive(Debug, Copy, Clone)] 107 | pub struct ZeroPageAddressingMode { 108 | addr: u8, 109 | } 110 | impl AddressingMode for ZeroPageAddressingMode { 111 | fn read_to_arg(&self, comp: &mut Compiler, _: bool) { 112 | dynasm!{comp.asm 113 | ; mov arg, [ram + self.addr as _] 114 | } 115 | } 116 | fn write_from_arg(&self, comp: &mut Compiler) { 117 | dynasm!{comp.asm 118 | ; mov [ram + self.addr as _], arg 119 | } 120 | } 121 | } 122 | 123 | #[derive(Debug, Copy, Clone)] 124 | pub struct ZeroPageXAddressingMode { 125 | addr: u8, 126 | } 127 | impl AddressingMode for ZeroPageXAddressingMode { 128 | fn read_to_arg(&self, comp: &mut Compiler, _: bool) { 129 | dynasm!{comp.asm 130 | ; mov rcx, self.addr as _ 131 | ; add cl, n_x 132 | ; mov arg, [ram + rcx] 133 | } 134 | } 135 | fn write_from_arg(&self, comp: &mut Compiler) { 136 | dynasm!{comp.asm 137 | ; mov rcx, self.addr as _ 138 | ; add cl, n_x 139 | ; mov [ram + rcx], arg 140 | } 141 | } 142 | } 143 | 144 | #[derive(Debug, Copy, Clone)] 145 | pub struct ZeroPageYAddressingMode { 146 | addr: u8, 147 | } 148 | impl AddressingMode for ZeroPageYAddressingMode { 149 | fn read_to_arg(&self, comp: &mut Compiler, _: bool) { 150 | dynasm!{comp.asm 151 | ; mov rcx, DWORD self.addr as _ 152 | ; add cl, n_y 153 | ; mov arg, [ram + rcx] 154 | } 155 | } 156 | fn write_from_arg(&self, comp: &mut Compiler) { 157 | dynasm!{comp.asm 158 | ; mov rcx, DWORD self.addr as _ 159 | ; add cl, n_y 160 | ; mov [ram + rcx], arg 161 | } 162 | } 163 | } 164 | 165 | #[derive(Debug, Copy, Clone)] 166 | pub struct AbsoluteAddressingMode { 167 | addr: u16, 168 | } 169 | impl AddressingMode for AbsoluteAddressingMode { 170 | fn read_to_arg(&self, comp: &mut Compiler, _: bool) { 171 | if self.addr < 0x2000 { 172 | let ram_address = self.addr % 0x800; 173 | dynasm!{comp.asm 174 | ; mov arg, [ram + ram_address as _] 175 | } 176 | } else { 177 | dynasm!{comp.asm 178 | ; mov rcx, self.addr as _ 179 | ;; call_read!(comp) 180 | } 181 | } 182 | } 183 | 184 | fn write_from_arg(&self, comp: &mut Compiler) { 185 | if self.addr < 0x2000 { 186 | let ram_address = self.addr % 0x800; 187 | dynasm!{comp.asm 188 | ; mov [ram + ram_address as _], arg 189 | } 190 | } else { 191 | dynasm!{comp.asm 192 | ; mov rcx, self.addr as _ 193 | ;; call_write!(comp) 194 | } 195 | } 196 | } 197 | } 198 | 199 | #[derive(Debug, Copy, Clone)] 200 | pub struct AbsoluteXAddressingMode { 201 | addr: u16, 202 | } 203 | impl AbsoluteXAddressingMode { 204 | fn tick_cycle(&self, comp: &mut Compiler, tick_cycle: bool) { 205 | if !tick_cycle || (self.addr & 0xFF00 == self.addr) { 206 | return; 207 | } 208 | // If x is greater than the number necessary to take it to the end of the page, 209 | // add a cycle. 210 | let page_boundary = (self.addr as u32 & 0xFF00) + 0x0100; 211 | let difference = page_boundary - self.addr as u32; 212 | 213 | dynasm!(comp.asm 214 | ; xor rcx, rcx 215 | ; cmp n_x, difference as _ 216 | ; setae cl 217 | ; add cyc, rcx 218 | ) 219 | } 220 | } 221 | impl AddressingMode for AbsoluteXAddressingMode { 222 | fn read_to_arg(&self, comp: &mut Compiler, tick_cycle: bool) { 223 | // adding X might make it step outside of RAM 224 | if self.addr < 0x1F00 { 225 | let ram_address = self.addr % 0x800; 226 | dynasm!{comp.asm 227 | ; mov rcx, ram_address as _ 228 | ; add rcx, r10 229 | ; mov arg, [ram + rcx] 230 | ;; self.tick_cycle(comp, tick_cycle) 231 | } 232 | } else { 233 | dynasm!{comp.asm 234 | ; mov rcx, self.addr as _ 235 | ; add rcx, r10 236 | ;; call_read!(comp) 237 | ;; self.tick_cycle(comp, tick_cycle) 238 | } 239 | } 240 | } 241 | 242 | fn write_from_arg(&self, comp: &mut Compiler) { 243 | // adding X might make it step outside of RAM 244 | if self.addr < 0x1F00 { 245 | let ram_address = self.addr % 0x800; 246 | dynasm!{comp.asm 247 | ; mov rcx, ram_address as _ 248 | ; add rcx, r10 249 | ; mov [ram + rcx], arg 250 | } 251 | } else { 252 | dynasm!{comp.asm 253 | ; mov rcx, self.addr as _ 254 | ; add rcx, r10 255 | ;; call_write!(comp) 256 | } 257 | } 258 | } 259 | } 260 | 261 | #[derive(Debug, Copy, Clone)] 262 | pub struct AbsoluteYAddressingMode { 263 | addr: u16, 264 | } 265 | impl AbsoluteYAddressingMode { 266 | fn tick_cycle(&self, comp: &mut Compiler, tick_cycle: bool) { 267 | if !tick_cycle || (self.addr & 0xFF00 == self.addr) { 268 | return; 269 | } 270 | // If y is greater than the number necessary to take it to the end of the page, 271 | // add a cycle. 272 | let page_boundary = (self.addr as u32 & 0xFF00) + 0x0100; 273 | let difference = page_boundary - self.addr as u32; 274 | 275 | dynasm!(comp.asm 276 | ; xor rcx, rcx 277 | ; cmp n_y, difference as _ 278 | ; setae cl 279 | ; add cyc, rcx 280 | ) 281 | } 282 | } 283 | impl AddressingMode for AbsoluteYAddressingMode { 284 | fn read_to_arg(&self, comp: &mut Compiler, tick_cycle: bool) { 285 | // adding Y might make it step outside of RAM 286 | if self.addr < 0x1F00 { 287 | let ram_address = self.addr % 0x800; 288 | dynasm!{comp.asm 289 | ; mov rcx, ram_address as _ 290 | ; add rcx, r11 291 | ; mov arg, [ram + rcx] 292 | ;; self.tick_cycle(comp, tick_cycle) 293 | } 294 | } else { 295 | dynasm!{comp.asm 296 | ; mov rcx, self.addr as _ 297 | ; add rcx, r11 298 | ;; call_read!(comp) 299 | ;; self.tick_cycle(comp, tick_cycle) 300 | } 301 | } 302 | } 303 | 304 | fn write_from_arg(&self, comp: &mut Compiler) { 305 | // adding Y might make it step outside of RAM 306 | if self.addr < 0x1F00 { 307 | let ram_address = self.addr % 0x800; 308 | dynasm!{comp.asm 309 | ; mov rcx, ram_address as _ 310 | ; add rcx, r11 311 | ; mov [ram + rcx], arg 312 | } 313 | } else { 314 | dynasm!{comp.asm 315 | ; mov rcx, self.addr as _ 316 | ; add rcx, r11 317 | ;; call_write!(comp) 318 | } 319 | } 320 | } 321 | } 322 | 323 | #[derive(Debug, Copy, Clone)] 324 | pub struct AccumulatorAddressingMode; 325 | impl AddressingMode for AccumulatorAddressingMode { 326 | fn read_to_arg(&self, comp: &mut Compiler, _: bool) { 327 | dynasm!{comp.asm 328 | ; mov arg, n_a 329 | } 330 | } 331 | fn write_from_arg(&self, comp: &mut Compiler) { 332 | dynasm!{comp.asm 333 | ; mov n_a, arg 334 | } 335 | } 336 | } 337 | 338 | #[derive(Debug, Copy, Clone)] 339 | pub struct IndirectXAddressingMode { 340 | addr: u8, 341 | } 342 | impl IndirectXAddressingMode { 343 | fn calc_addr(&self, comp: &mut Compiler) { 344 | dynasm!{comp.asm 345 | ; xor rcx, rcx 346 | ; mov cl, n_x 347 | ; add cl, self.addr as _ 348 | ; mov al, BYTE [ram + rcx] 349 | ; inc cl 350 | ; mov ah, BYTE [ram + rcx] 351 | ; mov cx, ax 352 | } 353 | } 354 | } 355 | impl AddressingMode for IndirectXAddressingMode { 356 | fn read_to_arg(&self, comp: &mut Compiler, _: bool) { 357 | dynasm!{comp.asm 358 | ;; self.calc_addr(comp) 359 | ;; fast_read!(comp) 360 | } 361 | } 362 | fn write_from_arg(&self, comp: &mut Compiler) { 363 | dynasm!{comp.asm 364 | ;; self.calc_addr(comp) 365 | ;; fast_write!(comp) 366 | } 367 | } 368 | } 369 | 370 | #[derive(Debug, Copy, Clone)] 371 | pub struct IndirectYAddressingMode { 372 | addr: u8, 373 | } 374 | impl IndirectYAddressingMode { 375 | fn calc_addr(&self, comp: &mut Compiler, tick_cycle: bool) { 376 | dynasm!{comp.asm 377 | ; mov rcx, self.addr as _ 378 | ; mov al, BYTE [ram + rcx] 379 | ; inc cl 380 | ; mov ah, BYTE [ram + rcx] 381 | ; mov cx, ax 382 | ; add cx, r11w 383 | } 384 | 385 | // Overwriting arg is safe here, because we only ever do the oops cycle on 386 | // reads, which 387 | // will immediately overwrite arg anyway. 388 | if tick_cycle { 389 | dynasm!{comp.asm 390 | ; xor r8, r8 391 | ; cmp ah, ch 392 | ; setne arg 393 | ; add cyc, r8 394 | } 395 | } 396 | } 397 | } 398 | impl AddressingMode for IndirectYAddressingMode { 399 | fn read_to_arg(&self, comp: &mut Compiler, tick_cycle: bool) { 400 | self.calc_addr(comp, tick_cycle); 401 | fast_read!(comp) 402 | } 403 | fn write_from_arg(&self, comp: &mut Compiler) { 404 | self.calc_addr(comp, false); 405 | fast_write!(comp) 406 | } 407 | } 408 | 409 | #[derive(Debug, Copy, Clone)] 410 | pub struct NoTickMode { 411 | pub mode: T, 412 | } 413 | impl AddressingMode for NoTickMode { 414 | fn read_to_arg(&self, comp: &mut Compiler, _: bool) { 415 | self.mode.read_to_arg(comp, false) 416 | } 417 | fn write_from_arg(&self, comp: &mut Compiler) { 418 | self.mode.write_from_arg(comp) 419 | } 420 | } 421 | 422 | 423 | impl<'a> Compiler<'a> { 424 | pub fn immediate(&mut self) -> ImmediateAddressingMode { 425 | ImmediateAddressingMode 426 | } 427 | pub fn absolute(&mut self) -> AbsoluteAddressingMode { 428 | AbsoluteAddressingMode { 429 | addr: self.read_w_incr_pc(), 430 | } 431 | } 432 | pub fn absolute_x(&mut self) -> AbsoluteXAddressingMode { 433 | AbsoluteXAddressingMode { 434 | addr: self.read_w_incr_pc(), 435 | } 436 | } 437 | pub fn absolute_y(&mut self) -> AbsoluteYAddressingMode { 438 | AbsoluteYAddressingMode { 439 | addr: self.read_w_incr_pc(), 440 | } 441 | } 442 | pub fn zero_page(&mut self) -> ZeroPageAddressingMode { 443 | ZeroPageAddressingMode { 444 | addr: self.read_incr_pc(), 445 | } 446 | } 447 | pub fn zero_page_x(&mut self) -> ZeroPageXAddressingMode { 448 | ZeroPageXAddressingMode { 449 | addr: self.read_incr_pc(), 450 | } 451 | } 452 | pub fn zero_page_y(&mut self) -> ZeroPageYAddressingMode { 453 | ZeroPageYAddressingMode { 454 | addr: self.read_incr_pc(), 455 | } 456 | } 457 | pub fn indirect_x(&mut self) -> IndirectXAddressingMode { 458 | IndirectXAddressingMode { 459 | addr: self.read_incr_pc(), 460 | } 461 | } 462 | pub fn indirect_y(&mut self) -> IndirectYAddressingMode { 463 | IndirectYAddressingMode { 464 | addr: self.read_incr_pc(), 465 | } 466 | } 467 | pub fn accumulator(&mut self) -> AccumulatorAddressingMode { 468 | AccumulatorAddressingMode 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /src/ppu/mod.rs: -------------------------------------------------------------------------------- 1 | use Settings; 2 | use cart::Cart; 3 | use memory::MemSegment; 4 | use screen::Screen; 5 | use std::cell::UnsafeCell; 6 | use std::cmp; 7 | use std::default::Default; 8 | use std::rc::Rc; 9 | 10 | mod ppu_reg; 11 | use ppu::ppu_reg::*; 12 | 13 | mod ppu_memory; 14 | use ppu::ppu_memory::*; 15 | 16 | mod sprite_rendering; 17 | use ppu::sprite_rendering::*; 18 | 19 | mod background_rendering; 20 | use ppu::background_rendering::*; 21 | 22 | pub const SCREEN_WIDTH: usize = 256; 23 | pub const SCREEN_HEIGHT: usize = 240; 24 | pub const SCREEN_BUFFER_SIZE: usize = SCREEN_WIDTH * SCREEN_HEIGHT; 25 | 26 | const CYCLES_PER_SCANLINE: u64 = 341; 27 | const SCANLINES_PER_FRAME: u64 = 262; 28 | const CYCLES_PER_FRAME: u64 = CYCLES_PER_SCANLINE * SCANLINES_PER_FRAME; 29 | 30 | #[derive(Debug, Copy, Clone, PartialEq)] 31 | #[repr(C)] 32 | pub struct Color(u8); 33 | impl Color { 34 | fn from_bits_truncate(val: u8) -> Color { 35 | Color(val & 0b0011_1111) 36 | } 37 | 38 | pub fn bits(&self) -> u8 { 39 | self.0 40 | } 41 | } 42 | 43 | #[derive(Debug, Copy, Clone, PartialEq)] 44 | pub enum PaletteSet { 45 | Background, 46 | Sprite, 47 | } 48 | 49 | impl PaletteSet { 50 | fn table(&self) -> u8 { 51 | match *self { 52 | PaletteSet::Background => 0x00, 53 | PaletteSet::Sprite => 0x10, 54 | } 55 | } 56 | } 57 | 58 | #[derive(Debug, Copy, Clone, PartialEq)] 59 | #[repr(C)] 60 | pub struct PaletteIndex { 61 | addr: u8, 62 | } 63 | 64 | const TRANSPARENT: PaletteIndex = PaletteIndex { addr: 0x00 }; 65 | 66 | impl PaletteIndex { 67 | pub fn from_packed(addr: u8) -> PaletteIndex { 68 | if addr & 0x03 == 0 { 69 | PaletteIndex { addr: 0 } 70 | } else { 71 | PaletteIndex { addr: addr } 72 | } 73 | } 74 | 75 | pub fn from_unpacked(set: PaletteSet, palette_id: u8, color_id: u8) -> PaletteIndex { 76 | if color_id == 0 { 77 | return PaletteIndex { addr: 0 }; 78 | } 79 | let mut addr: u8 = 0x00; 80 | addr |= set.table(); 81 | addr |= (palette_id & 0x03) << 2; 82 | addr |= color_id & 0x03; 83 | PaletteIndex { addr: addr } 84 | } 85 | 86 | #[cfg(not(feature = "vectorize"))] 87 | fn to_index(self) -> usize { 88 | self.addr as usize 89 | } 90 | 91 | fn is_transparent(&self) -> bool { 92 | self.addr == 0 93 | } 94 | } 95 | 96 | #[derive(Debug, Copy, Clone)] 97 | pub struct TilePattern { 98 | lo: u8, 99 | hi: u8, 100 | } 101 | 102 | pub const NO_TILE: TilePattern = TilePattern { lo: 0, hi: 0 }; 103 | 104 | impl Default for TilePattern { 105 | fn default() -> TilePattern { 106 | NO_TILE 107 | } 108 | } 109 | 110 | impl TilePattern { 111 | fn get_color_in_pattern(&self, fine_x: u32) -> u8 { 112 | let lo = self.lo; 113 | let hi = self.hi; 114 | let shift = 0x07 - fine_x; 115 | let color_id_lo = lo.wrapping_shr(shift) & 0x01; 116 | let color_id_hi = (hi.wrapping_shr(shift) & 0x01) << 1; 117 | color_id_lo | color_id_hi 118 | } 119 | } 120 | 121 | pub struct PPU { 122 | settings: Rc, 123 | 124 | reg: PPUReg, 125 | ppudata_read_buffer: u8, 126 | ppu_mem: PPUMemory, 127 | 128 | screen: Box, 129 | palette_buffer: Box<[PaletteIndex; SCREEN_BUFFER_SIZE]>, 130 | screen_buffer: Box<[Color; SCREEN_BUFFER_SIZE]>, 131 | 132 | sprite_data: SpriteRenderer, 133 | background_data: BackgroundRenderer, 134 | 135 | global_cyc: u64, 136 | cyc: u16, 137 | sl: i16, 138 | frame: u32, 139 | 140 | next_vblank_ppu_cyc: u64, 141 | next_vblank_cpu_cyc: u64, 142 | } 143 | 144 | #[derive(Copy, Debug, PartialEq, Clone)] 145 | pub enum StepResult { 146 | NMI, 147 | Continue, 148 | } 149 | 150 | fn div_rem(num: u64, den: u64) -> (u64, u64) { 151 | (num / den, num % den) 152 | } 153 | 154 | fn ppu_to_cpu_cyc(ppu_cyc: u64) -> u64 { 155 | let (div, rem) = div_rem(ppu_cyc, 3); 156 | if rem == 0 { 157 | div 158 | } else { 159 | div + 1 160 | } 161 | } 162 | 163 | fn cpu_to_ppu_cyc(cpu_cyc: u64) -> u64 { 164 | cpu_cyc * 3 165 | } 166 | 167 | fn cyc_to_px(ppu_cyc: u64) -> usize { 168 | let mut pixel: usize = 0; 169 | let mut rem = ppu_cyc; 170 | 171 | rem += 241 * CYCLES_PER_SCANLINE; // Skip to the position at power-on. 172 | 173 | let (frames, rem_t) = div_rem(rem, CYCLES_PER_FRAME); 174 | rem = rem_t; 175 | pixel += frames as usize * SCREEN_BUFFER_SIZE; 176 | 177 | // Skip the pre-render scanline. 178 | rem = rem.saturating_sub(CYCLES_PER_SCANLINE); 179 | 180 | // Cut off the VBLANK scanlines. 181 | rem = cmp::min(rem, SCREEN_HEIGHT as u64 * CYCLES_PER_SCANLINE); 182 | 183 | let (scanlines, rem_t) = div_rem(rem, CYCLES_PER_SCANLINE); 184 | rem = rem_t; 185 | pixel += scanlines as usize * SCREEN_WIDTH; 186 | 187 | rem = rem.saturating_sub(1); // Skip idle cycle 188 | rem = cmp::min(rem, SCREEN_WIDTH as u64); // Cut off HBLANK 189 | 190 | pixel += rem as usize; 191 | pixel 192 | } 193 | 194 | impl PPU { 195 | pub fn new(settings: Rc, cart: Rc>, screen: Box) -> PPU { 196 | PPU { 197 | settings: settings, 198 | 199 | reg: Default::default(), 200 | ppudata_read_buffer: 0, 201 | ppu_mem: PPUMemory::new(cart), 202 | 203 | palette_buffer: Box::new([TRANSPARENT; SCREEN_BUFFER_SIZE]), 204 | screen_buffer: Box::new([Color::from_bits_truncate(0x00); SCREEN_BUFFER_SIZE]), 205 | screen: screen, 206 | 207 | sprite_data: Default::default(), 208 | background_data: Default::default(), 209 | 210 | global_cyc: 0, 211 | cyc: 0, 212 | sl: 241, 213 | frame: 0, 214 | 215 | next_vblank_ppu_cyc: 1, 216 | next_vblank_cpu_cyc: ppu_to_cpu_cyc(1), 217 | } 218 | } 219 | 220 | pub fn run_to(&mut self, cpu_cycle: u64) -> StepResult { 221 | let start = self.global_cyc; 222 | let stop = cpu_to_ppu_cyc(cpu_cycle); 223 | 224 | let start_px = cyc_to_px(start); 225 | let delta_px = cyc_to_px(stop) - start_px; 226 | let start_px = start_px % SCREEN_BUFFER_SIZE; 227 | let stop_px = start_px + delta_px; 228 | 229 | let rendering_enabled = self.reg.ppumask.rendering_enabled(); 230 | 231 | let mut hit_nmi = false; 232 | while self.global_cyc < stop { 233 | self.tick_cycle(); 234 | self.run_cycle(rendering_enabled, &mut hit_nmi); 235 | } 236 | 237 | if self.settings.graphics_enabled { 238 | if self.reg.ppumask.contains(S_BCK) { 239 | self.background_data 240 | .render(&mut self.palette_buffer, start_px, stop_px, &self.reg); 241 | } else { 242 | let slice = &mut self.palette_buffer[start_px..stop_px]; 243 | for dest in slice.iter_mut() { 244 | *dest = TRANSPARENT; 245 | } 246 | } 247 | if self.reg.ppumask.contains(S_SPR) { 248 | self.sprite_data 249 | .render(&mut self.palette_buffer, &mut self.reg, start_px, stop_px); 250 | } 251 | 252 | self.colorize(start_px, stop_px); 253 | } 254 | 255 | if hit_nmi { 256 | StepResult::NMI 257 | } else { 258 | StepResult::Continue 259 | } 260 | } 261 | 262 | /// Returns the CPU cycle number representing the next time the CPU should 263 | /// run the PPU. When the CPU cycle reaches this number, the CPU must run 264 | /// the PPU. 265 | pub fn requested_run_cycle(&self) -> u64 { 266 | self.next_vblank_cpu_cyc 267 | } 268 | 269 | fn tick_cycle(&mut self) { 270 | self.global_cyc += 1; 271 | self.cyc += 1; 272 | if self.cyc == 341 { 273 | self.cyc = 0; 274 | self.sl += 1; 275 | if self.sl == 261 { 276 | self.sl = -1; 277 | self.frame += 1; 278 | } 279 | } 280 | } 281 | 282 | fn run_cycle(&mut self, rendering_enabled: bool, hit_nmi: &mut bool) { 283 | if let -1...239 = self.sl { 284 | if rendering_enabled && self.settings.graphics_enabled { 285 | self.background_data 286 | .run_cycle(self.cyc, self.sl, &mut self.reg, &mut self.ppu_mem); 287 | } 288 | } 289 | match (self.cyc, self.sl) { 290 | (_, -1) => self.prerender_scanline(), 291 | 292 | // Visible scanlines 293 | (0, 0...239) => { 294 | if self.settings.graphics_enabled { 295 | self.sprite_data 296 | .sprite_eval(self.sl as u16, &self.reg, &mut self.ppu_mem) 297 | } 298 | } 299 | (_, 0...239) => (), 300 | 301 | (_, 240) => (), //Post-render idle scanline 302 | (1, 241) => self.start_vblank(hit_nmi), 303 | (_, 241...260) => (), //VBlank lines 304 | _ => (), 305 | } 306 | } 307 | 308 | fn prerender_scanline(&mut self) { 309 | if self.cyc == 1 { 310 | self.reg.ppustat.remove(VBLANK | SPRITE_0 | SPRITE_OVERFLOW); 311 | } 312 | if self.cyc == 339 && self.frame % 2 == 1 { 313 | self.tick_cycle() 314 | } 315 | } 316 | 317 | fn start_vblank(&mut self, hit_nmi: &mut bool) { 318 | self.next_vblank_ppu_cyc += CYCLES_PER_FRAME; 319 | self.next_vblank_cpu_cyc = ppu_to_cpu_cyc(self.next_vblank_ppu_cyc); 320 | 321 | let buf = &self.screen_buffer; 322 | self.screen.draw(buf); 323 | 324 | if self.frame > 0 { 325 | self.reg.ppustat.insert(VBLANK); 326 | *hit_nmi |= self.reg.ppuctrl.generate_vblank_nmi(); 327 | } 328 | } 329 | 330 | #[cfg(feature = "vectorize")] 331 | fn colorize(&mut self, start: usize, stop: usize) { 332 | use std::mem; 333 | use std::cmp; 334 | use simd::u8x16; 335 | use simd::x86::ssse3::Ssse3U8x16; 336 | 337 | let (background_pal, sprite_pal) = self.ppu_mem.get_palettes(); 338 | let index_bytes: &[u8; SCREEN_BUFFER_SIZE] = 339 | unsafe { mem::transmute(&self.palette_buffer) }; 340 | let color_bytes: &mut [u8; SCREEN_BUFFER_SIZE] = 341 | unsafe { mem::transmute(&mut self.screen_buffer) }; 342 | 343 | let mut start = start; 344 | 345 | while start < stop { 346 | start = cmp::min(start, SCREEN_BUFFER_SIZE - 16); 347 | let palette_idx = u8x16::load(index_bytes, start); 348 | 349 | let table: u8x16 = palette_idx >> 4; 350 | let use_sprite_table = table.ne(u8x16::splat(0)); 351 | let color_id = palette_idx & u8x16::splat(0b0000_1111); 352 | 353 | let background_shuf = background_pal.shuffle_bytes(color_id); 354 | let sprite_shuf = sprite_pal.shuffle_bytes(color_id); 355 | 356 | let final_color = use_sprite_table.select(sprite_shuf, background_shuf); 357 | final_color.store(&mut *color_bytes, start); 358 | start += 16; 359 | } 360 | } 361 | 362 | #[cfg(not(feature = "vectorize"))] 363 | fn colorize(&mut self, start: usize, stop: usize) { 364 | let color_slice = &mut self.screen_buffer[start..stop]; 365 | let index_slice = &self.palette_buffer[start..stop]; 366 | 367 | for (src, dest) in index_slice.iter().zip(color_slice.iter_mut()) { 368 | *dest = self.ppu_mem.read_palette(*src); 369 | } 370 | } 371 | 372 | #[cfg(feature = "debug_features")] 373 | pub fn cycle(&self) -> u16 { 374 | self.cyc 375 | } 376 | 377 | #[cfg(feature = "debug_features")] 378 | pub fn scanline(&self) -> i16 { 379 | self.sl 380 | } 381 | 382 | #[cfg(feature = "debug_features")] 383 | pub fn vram_addr(&self) -> u16 { 384 | self.reg.v 385 | } 386 | 387 | pub fn frame(&self) -> u32 { 388 | self.frame 389 | } 390 | 391 | #[cfg(feature = "debug_features")] 392 | pub fn mouse_pick(&self, px_x: i32, px_y: i32) { 393 | self.background_data.mouse_pick(&self.reg, px_x, px_y); 394 | self.sprite_data.mouse_pick(px_x, px_y); 395 | } 396 | 397 | pub fn rendering_enabled(&self) -> bool { 398 | self.reg.ppumask.rendering_enabled() 399 | } 400 | } 401 | 402 | impl MemSegment for PPU { 403 | fn read(&mut self, idx: u16) -> u8 { 404 | match idx % 8 { 405 | 0x0004 => self.sprite_data.read(self.reg.oamaddr as u16), 406 | 0x0007 => { 407 | let addr = self.reg.v; 408 | match addr { 409 | 0x0000...0x3EFF => { 410 | let old_buffer = self.ppudata_read_buffer; 411 | self.ppudata_read_buffer = self.ppu_mem.read(addr); 412 | self.reg.incr_ppuaddr(); 413 | old_buffer 414 | } 415 | 0x3F00...0x3FFF => { 416 | let read_result = self.ppu_mem.read(addr); 417 | self.reg.incr_ppuaddr(); 418 | self.ppudata_read_buffer = self.ppu_mem.read_bypass_palette(addr); 419 | read_result 420 | } 421 | x => invalid_address!(x), 422 | } 423 | } 424 | _ => self.reg.read(idx), 425 | } 426 | } 427 | 428 | fn write(&mut self, idx: u16, val: u8) { 429 | match idx % 8 { 430 | 0x0004 => { 431 | self.sprite_data.write(self.reg.oamaddr as u16, val); 432 | self.reg.incr_oamaddr(); 433 | } 434 | 0x0007 => { 435 | self.ppu_mem.write(self.reg.v, val); 436 | self.reg.incr_ppuaddr(); 437 | } 438 | _ => self.reg.write(idx, val), 439 | } 440 | } 441 | } 442 | 443 | #[cfg(test)] 444 | mod tests { 445 | use super::*; 446 | use cart::{Cart, ScreenMode}; 447 | use mappers::create_test_mapper; 448 | use memory::MemSegment; 449 | use ppu::ppu_reg::PPUCtrl; 450 | use screen::DummyScreen; 451 | use std::cell::UnsafeCell; 452 | use std::rc::Rc; 453 | 454 | pub fn create_test_ppu() -> PPU { 455 | create_test_ppu_with_rom(vec![0u8; 0x1000]) 456 | } 457 | 458 | pub fn create_test_ppu_with_rom(chr_rom: Vec) -> PPU { 459 | let mapper = create_test_mapper(vec![0u8; 0x1000], chr_rom, ScreenMode::FourScreen); 460 | let cart = Cart::new(mapper); 461 | let settings = Settings { 462 | graphics_enabled: true, 463 | ..Default::default() 464 | }; 465 | PPU::new( 466 | Rc::new(settings), 467 | Rc::new(UnsafeCell::new(cart)), 468 | Box::new(DummyScreen::default()), 469 | ) 470 | } 471 | 472 | pub fn create_test_ppu_with_mirroring(mode: ScreenMode) -> PPU { 473 | let mapper = create_test_mapper(vec![0u8; 0x1000], vec![0u8; 0x1000], mode); 474 | let cart = Cart::new(mapper); 475 | let settings = Settings { 476 | graphics_enabled: true, 477 | ..Default::default() 478 | }; 479 | PPU::new( 480 | Rc::new(settings), 481 | Rc::new(UnsafeCell::new(cart)), 482 | Box::new(DummyScreen::default()), 483 | ) 484 | } 485 | 486 | #[test] 487 | fn reading_oamdata_doesnt_increment_oamaddr() { 488 | let mut ppu = create_test_ppu(); 489 | ppu.reg.oamaddr = 0; 490 | ppu.read(0x2004); 491 | assert_eq!(ppu.reg.oamaddr, 0); 492 | } 493 | 494 | #[test] 495 | fn writing_oamdata_increments_oamaddr() { 496 | let mut ppu = create_test_ppu(); 497 | ppu.reg.oamaddr = 0; 498 | ppu.write(0x2004, 12); 499 | assert_eq!(ppu.reg.oamaddr, 1); 500 | ppu.reg.oamaddr = 255; 501 | ppu.write(0x2004, 12); 502 | assert_eq!(ppu.reg.oamaddr, 0); 503 | } 504 | 505 | #[test] 506 | fn ppu_can_read_chr_rom() { 507 | let mut chr_rom = vec![0u8; 0x2000]; 508 | chr_rom[0x0ABC] = 12; 509 | chr_rom[0x0DBA] = 212; 510 | let mut ppu = create_test_ppu_with_rom(chr_rom); 511 | 512 | ppu.reg.v = 0x0ABC; 513 | ppu.read(0x2007); // Dummy Read 514 | assert_eq!(ppu.read(0x2007), 12); 515 | 516 | ppu.reg.v = 0x0DBA; 517 | ppu.read(0x2007); // Dummy Read 518 | assert_eq!(ppu.read(0x2007), 212); 519 | } 520 | 521 | #[test] 522 | fn ppu_can_read_write_vram() { 523 | let mut ppu = create_test_ppu(); 524 | 525 | ppu.reg.v = 0x2ABC; 526 | ppu.write(0x2007, 12); 527 | ppu.reg.v = 0x2ABC; 528 | ppu.read(0x2007); // Dummy read 529 | assert_eq!(ppu.read(0x2007), 12); 530 | 531 | ppu.reg.v = 0x2DBA; 532 | ppu.write(0x2007, 212); 533 | ppu.reg.v = 0x2DBA; 534 | ppu.read(0x2007); // Dummy Read 535 | assert_eq!(ppu.read(0x2007), 212); 536 | 537 | // Mirroring 538 | ppu.reg.v = 0x2EFC; 539 | ppu.write(0x2007, 128); 540 | ppu.reg.v = 0x3EFC; 541 | ppu.read(0x2007); // Dummy Read 542 | assert_eq!(ppu.read(0x2007), 128); 543 | } 544 | 545 | #[test] 546 | fn ppu_needs_no_dummy_read_for_palette_data() { 547 | let mut ppu = create_test_ppu(); 548 | ppu.reg.v = 0x3F16; 549 | ppu.write(0x2007, 21); 550 | ppu.reg.v = 0x3F16; 551 | assert_eq!(ppu.read(0x2007), 21); 552 | } 553 | 554 | #[test] 555 | fn accessing_ppudata_increments_ppuaddr() { 556 | let mut ppu = create_test_ppu(); 557 | ppu.reg.v = 0x2000; 558 | ppu.read(0x2007); 559 | assert_eq!(ppu.reg.v, 0x2001); 560 | ppu.write(0x2007, 0); 561 | assert_eq!(ppu.reg.v, 0x2002); 562 | } 563 | 564 | #[test] 565 | fn accessing_ppudata_increments_ppuaddr_by_32_when_ctrl_flag_is_set() { 566 | let mut ppu = create_test_ppu(); 567 | ppu.reg.ppuctrl = PPUCtrl::new(0b0000_0100); 568 | ppu.reg.v = 0x2000; 569 | ppu.read(0x2007); 570 | assert_eq!(ppu.reg.v, 0x2020); 571 | ppu.write(0x2007, 0); 572 | assert_eq!(ppu.reg.v, 0x2040); 573 | } 574 | } 575 | --------------------------------------------------------------------------------