├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Cargo.toml ├── LICENSE ├── README.md ├── demo.gif ├── nestest_out.log ├── roms ├── nestest.log └── nestest.nes └── src ├── app.rs ├── imgui_wgpu ├── LICENSE.md ├── imgui.frag ├── imgui.frag.spv ├── imgui.vert ├── imgui.vert.spv └── mod.rs ├── main.rs └── nes ├── bus.rs ├── cartridge.rs ├── controller.rs ├── cpu.rs ├── disasm.rs ├── dma.rs ├── logger.rs ├── mappers.rs ├── mod.rs ├── ppu.rs └── ram.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | _* -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "lldb", 5 | "request": "launch", 6 | "name": "run nes-rust", 7 | "program": "${workspaceRoot}\\target\\debug\\nes-rust.exe", 8 | "args": [], 9 | "cwd": "${workspaceRoot}", 10 | "sourceFileMap": {} 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "cargo", 8 | "command": "build", 9 | "problemMatcher": [ 10 | "$rustc" 11 | ], 12 | "group": "build", 13 | "label": "rust: cargo build" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nes-rust" 3 | version = "0.1.0" 4 | authors = ["Anton "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | "clap" = "2" 11 | wgpu = "0.5" 12 | # glsl-to-spirv = { version = "0.1", optional = true } 13 | # log = "0.4" 14 | imgui = "0.4" 15 | winit = "0.22" 16 | # raw-window-handle = "0.3" 17 | image = "0.23" 18 | futures = "0.3" 19 | imgui-winit-support = { version = "0.4", default-features = false, features = ["winit-22"] } 20 | rand = "0.7" 21 | glob = "0.3" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Anton Novoselov 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nes-rust 2 | 3 | NES emulator in Rust with GUI 4 | 5 | ![](demo.gif) 6 | 7 | ## How to run 8 | 9 | ``` 10 | > cargo run 11 | ``` 12 | 13 | 14 | ## Acknowledgements & Resources 15 | 16 | That was an educational project, thanks to great resources available out there: 17 | 18 | * Nesdev Wiki: http://wiki.nesdev.com/w/index.php/Nesdev_Wiki 19 | * 6502 instructions: http://www.obelisk.me.uk/6502/reference.html 20 | * javidx9 videos and sources: https://www.youtube.com/watch?v=F8kx56OZQhg 21 | * other C++ emulator: https://github.com/amhndu/SimpleNES 22 | * nestest.rom from: https://wiki.nesdev.com/w/index.php/Emulator_tests 23 | * ImGui wgpu backend was taken from here: https://github.com/unconed/imgui-wgpu-rs and hacked to add basic support for updating/reuploading textures. 24 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoselov-ab/nes-rust/cae249307f320ec9b6cf1ac96806b1fe6247ede8/demo.gif -------------------------------------------------------------------------------- /roms/nestest.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoselov-ab/nes-rust/cae249307f320ec9b6cf1ac96806b1fe6247ede8/roms/nestest.nes -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | #![allow(unused)] 3 | 4 | use crate::imgui_wgpu::Renderer; 5 | use crate::nes; 6 | use futures::executor::block_on; 7 | use glob::glob; 8 | use imgui::*; 9 | use imgui_winit_support; 10 | use std::path::PathBuf; 11 | use std::rc::Rc; 12 | use std::time::Instant; 13 | use wgpu::{Device, Queue}; 14 | use winit::{ 15 | dpi::LogicalSize, 16 | event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, 17 | event_loop::{ControlFlow, EventLoop}, 18 | window::Window, 19 | }; 20 | 21 | fn find_roms() -> glob::Paths { 22 | let exe_path = std::env::current_exe(); 23 | let rom_path = exe_path.unwrap().parent().unwrap().join("../../roms"); 24 | 25 | glob(rom_path.join("**/*.nes").to_str().unwrap()).unwrap() 26 | } 27 | 28 | fn to_rgb01(color: [i32; 4]) -> [f32; 4] { 29 | [ 30 | color[0] as f32 / 255.0, 31 | color[1] as f32 / 255.0, 32 | color[2] as f32 / 255.0, 33 | color[3] as f32 / 255.0, 34 | ] 35 | } 36 | 37 | struct UiTexture { 38 | size: (usize, usize), 39 | data: Vec, 40 | texture_id: TextureId, 41 | } 42 | 43 | impl UiTexture { 44 | fn new(renderer: &mut Renderer, device: &Device, size: (usize, usize)) -> Self { 45 | let texture_id = renderer.create_texture(&device, size.0 as u32, size.1 as u32); 46 | UiTexture { 47 | size: size, 48 | data: vec![0; size.0 * size.1 * 4], 49 | texture_id: texture_id, 50 | } 51 | } 52 | 53 | pub fn update(&mut self, renderer: &mut Renderer, device: &Device, mut queue: &mut Queue) { 54 | // Uploaded updated screen texture data 55 | renderer.update_texture( 56 | self.texture_id, 57 | &device, 58 | &mut queue, 59 | &self.data, 60 | self.size.0 as u32, 61 | self.size.1 as u32, 62 | ); 63 | } 64 | 65 | pub fn get_size(&self, scale: f32) -> [f32; 2] { 66 | [(self.size.0 as f32) * scale, (self.size.1 as f32) * scale] 67 | } 68 | 69 | pub fn set_pixel(&mut self, x: usize, y: usize, c: u32) { 70 | let r = ((c & 0xFF0000) >> 16) as u8; 71 | let g = ((c & 0xFF00) >> 8) as u8; 72 | let b = ((c & 0xFF) >> 0) as u8; 73 | 74 | let x0 = x * 4; 75 | let y0 = y * 4; 76 | let pos = y0 * self.size.0; 77 | self.data[pos + x0..pos + x0 + 4].copy_from_slice(&[r, g, b, 0xFF]); 78 | } 79 | } 80 | 81 | // Screen is used to store and update screen buffer and draw it as window with a texture 82 | struct ScreenBuffer { 83 | ui_scale: f32, 84 | texture: UiTexture, 85 | } 86 | 87 | impl ScreenBuffer { 88 | fn new(renderer: &mut Renderer, device: &Device) -> Self { 89 | ScreenBuffer { 90 | ui_scale: 3.0_f32, 91 | texture: UiTexture::new(renderer, device, nes::ppu::SCREEN_SIZE), 92 | } 93 | } 94 | 95 | fn draw_ui(&mut self, ui: &imgui::Ui) { 96 | // Screen window 97 | let window = imgui::Window::new(im_str!("Screen")).always_auto_resize(true); 98 | window 99 | .position([370.0, 5.0], Condition::Once) 100 | .build(&ui, || { 101 | Image::new( 102 | self.texture.texture_id, 103 | self.texture.get_size(self.ui_scale), 104 | ) 105 | .build(&ui); 106 | ui.drag_float(im_str!("Scale"), &mut self.ui_scale).build(); 107 | }); 108 | } 109 | 110 | fn update( 111 | &mut self, 112 | ppu: &nes::ppu::Ppu, 113 | renderer: &mut Renderer, 114 | device: &Device, 115 | queue: &mut Queue, 116 | ) { 117 | // Update pixels in screen buffer from ppu's screen 118 | for x in 0..self.texture.size.0 { 119 | for y in 0..self.texture.size.1 { 120 | self.texture.set_pixel(x, y, ppu.screen.get_pixel(x, y)); 121 | } 122 | } 123 | 124 | self.texture.update(renderer, device, queue); 125 | } 126 | } 127 | 128 | struct StateWindow { 129 | textures: [UiTexture; 2], 130 | } 131 | 132 | impl StateWindow { 133 | fn new(renderer: &mut Renderer, device: &Device) -> Self { 134 | StateWindow { 135 | textures: [ 136 | UiTexture::new(renderer, device, (128, 128)), 137 | UiTexture::new(renderer, device, (128, 128)), 138 | ], 139 | } 140 | } 141 | 142 | fn draw_ui(&mut self, ui: &imgui::Ui, emulator: &mut nes::Emulator) { 143 | // Window with CPU state 144 | let window = imgui::Window::new(im_str!("State")); 145 | window 146 | .size([425.0, 600.0], Condition::Once) 147 | .position([1170.0, 5.0], Condition::Once) 148 | .build(&ui, || { 149 | ui.text(format!("FPS: {:.0}", emulator.frame_time.fps)); 150 | ui.text(format!("Total Clocks: {}", emulator.clock)); 151 | ui.text(format!("PC: {:#X}", emulator.cpu.PC)); 152 | 153 | for i in 0..2 { 154 | Image::new(self.textures[i].texture_id, self.textures[i].get_size(1.5)) 155 | .build(&ui); 156 | ui.same_line(0.0); 157 | } 158 | }); 159 | } 160 | 161 | fn update( 162 | &mut self, 163 | ppu: &mut nes::ppu::Ppu, 164 | renderer: &mut Renderer, 165 | device: &Device, 166 | queue: &mut Queue, 167 | ) { 168 | let palette = 0; 169 | 170 | // Get and draw CHR ROM 171 | for i in 0..2 as usize { 172 | for tile_y in 0..16 { 173 | for tile_x in 0..16 { 174 | let offset = tile_y * 256 + tile_x * 16; 175 | for row in 0..8 { 176 | let mut tile_lsb = 177 | ppu.ppu_read((i as u16) * 0x1000 + offset + row + 0x0000); 178 | let mut tile_msb = 179 | ppu.ppu_read((i as u16) * 0x1000 + offset + row + 0x0008); 180 | 181 | for col in 0..8 { 182 | let pixel = (tile_msb & 0x01) << 1 | (tile_lsb & 0x01); 183 | 184 | tile_lsb >>= 1; 185 | tile_msb >>= 1; 186 | 187 | self.textures[i].set_pixel( 188 | (tile_x * 8 + (7 - col)) as usize, 189 | (tile_y * 8 + row) as usize, 190 | ppu.get_color_from_pal(palette, pixel), 191 | ); 192 | } 193 | } 194 | } 195 | } 196 | self.textures[i].update(renderer, device, queue); 197 | } 198 | } 199 | } 200 | 201 | pub struct NESApp { 202 | rom_files: Vec, 203 | emulator: nes::Emulator, 204 | } 205 | 206 | impl NESApp { 207 | pub fn new() -> Self { 208 | let roms = find_roms().map(|res| res.unwrap()).collect(); 209 | 210 | NESApp { 211 | rom_files: roms, 212 | emulator: nes::Emulator::new(), 213 | } 214 | } 215 | 216 | fn draw_ui(&mut self, ui: &imgui::Ui) { 217 | // Window with list of ROMs 218 | let window = imgui::Window::new(im_str!("ROMs")); 219 | window 220 | .size([350.0, 600.0], Condition::Once) 221 | .position([5.0, 5.0], Condition::Once) 222 | .build(&ui, || { 223 | for rom_file in &self.rom_files { 224 | let filename = ImString::new(rom_file.file_name().unwrap().to_str().unwrap()); 225 | if ui.button(&filename, [0 as f32, 0 as f32]) { 226 | self.emulator.load_rom(rom_file); 227 | } 228 | } 229 | }); 230 | 231 | // Help Window 232 | let window = imgui::Window::new(im_str!("Help")); 233 | window 234 | .size([350.0, 160.0], Condition::Once) 235 | .position([5.0, 660.0], Condition::Once) 236 | .build(&ui, || { 237 | ui.text(im_str!( 238 | "Select ROM file, to control use keys:\nA,S,Z,X,\nArrow Keys\n\nHave fun!" 239 | )); 240 | }); 241 | 242 | // Test Logger 243 | let logger = self.emulator.logger.borrow(); 244 | let log_txt = String::from_utf8_lossy(&logger.bytes); 245 | 246 | let window = imgui::Window::new(im_str!("Test Logger")); 247 | window 248 | .size([425.0, 160.0], Condition::Once) 249 | .position([1170.0, 660.0], Condition::Once) 250 | .build(&ui, || { 251 | ui.text(format!("{:?}", logger.bytes[0])); 252 | ui.text(log_txt.clone()); 253 | }); 254 | } 255 | 256 | fn set_key_state(&mut self, code: VirtualKeyCode, state: bool) { 257 | let b = match code { 258 | VirtualKeyCode::X => 0x80, 259 | VirtualKeyCode::Z => 0x40, 260 | VirtualKeyCode::A => 0x20, 261 | VirtualKeyCode::S => 0x10, 262 | VirtualKeyCode::Up => 0x08, 263 | VirtualKeyCode::Down => 0x04, 264 | VirtualKeyCode::Left => 0x02, 265 | VirtualKeyCode::Right => 0x01, 266 | _ => return, 267 | }; 268 | 269 | let mut controller = self.emulator.controllers[0].borrow_mut(); 270 | if state { 271 | controller.input |= b; 272 | } else { 273 | controller.input &= !b; 274 | } 275 | } 276 | 277 | pub fn run(mut self: Rc) { 278 | // Set up window and GPU 279 | let event_loop = EventLoop::new(); 280 | let mut hidpi_factor = 1.0; 281 | let (window, mut size, surface) = { 282 | let window = Window::new(&event_loop).unwrap(); 283 | window.set_inner_size(LogicalSize { 284 | width: 1600.0, 285 | height: 900.0, 286 | }); 287 | window.set_title("nes-rust"); 288 | let size = window.inner_size(); 289 | 290 | let surface = wgpu::Surface::create(&window); 291 | 292 | (window, size, surface) 293 | }; 294 | 295 | let adapter = block_on(wgpu::Adapter::request( 296 | &wgpu::RequestAdapterOptions { 297 | power_preference: wgpu::PowerPreference::HighPerformance, 298 | compatible_surface: Some(&surface), 299 | }, 300 | wgpu::BackendBit::PRIMARY, 301 | )) 302 | .unwrap(); 303 | 304 | let (mut device, mut queue) = block_on(adapter.request_device(&wgpu::DeviceDescriptor { 305 | extensions: wgpu::Extensions { 306 | anisotropic_filtering: false, 307 | }, 308 | limits: wgpu::Limits::default(), 309 | })); 310 | 311 | // Set up swap chain 312 | let mut sc_desc = wgpu::SwapChainDescriptor { 313 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 314 | format: wgpu::TextureFormat::Bgra8Unorm, 315 | width: size.width as u32, 316 | height: size.height as u32, 317 | present_mode: wgpu::PresentMode::Mailbox, 318 | }; 319 | 320 | let mut swap_chain = device.create_swap_chain(&surface, &sc_desc); 321 | 322 | // Set up dear imgui 323 | let mut imgui = imgui::Context::create(); 324 | let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); 325 | platform.attach_window( 326 | imgui.io_mut(), 327 | &window, 328 | imgui_winit_support::HiDpiMode::Default, 329 | ); 330 | imgui.set_ini_filename(None); 331 | 332 | let font_size = (13.0 * hidpi_factor) as f32; 333 | imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; 334 | 335 | imgui.fonts().add_font(&[FontSource::DefaultFontData { 336 | config: Some(imgui::FontConfig { 337 | oversample_h: 1, 338 | pixel_snap_h: true, 339 | size_pixels: font_size, 340 | ..Default::default() 341 | }), 342 | }]); 343 | 344 | // Restyle a bit 345 | let style = imgui.style_mut(); 346 | style.window_rounding = 8.0; 347 | style.scrollbar_rounding = 8.0; 348 | style.frame_rounding = 8.0; 349 | style[imgui::StyleColor::TitleBg] = to_rgb01([110, 110, 100, 62]); 350 | style[imgui::StyleColor::TitleBgCollapsed] = to_rgb01([110, 110, 100, 52]); 351 | style[imgui::StyleColor::TitleBgActive] = to_rgb01([110, 110, 100, 87]); 352 | style[imgui::StyleColor::Header] = to_rgb01([110, 110, 110, 52]); 353 | style[imgui::StyleColor::HeaderHovered] = to_rgb01([110, 110, 110, 92]); 354 | style[imgui::StyleColor::HeaderActive] = to_rgb01([110, 110, 110, 72]); 355 | style[imgui::StyleColor::ScrollbarBg] = to_rgb01([110, 110, 110, 12]); 356 | style[imgui::StyleColor::ScrollbarGrab] = to_rgb01([110, 110, 110, 52]); 357 | style[imgui::StyleColor::ScrollbarGrabHovered] = to_rgb01([110, 110, 110, 92]); 358 | style[imgui::StyleColor::ScrollbarGrabActive] = to_rgb01([110, 110, 110, 72]); 359 | style[imgui::StyleColor::SliderGrab] = to_rgb01([110, 110, 110, 52]); 360 | style[imgui::StyleColor::SliderGrabActive] = to_rgb01([110, 110, 110, 72]); 361 | style[imgui::StyleColor::Button] = to_rgb01([182, 182, 182, 60]); 362 | style[imgui::StyleColor::ButtonHovered] = to_rgb01([182, 182, 182, 200]); 363 | style[imgui::StyleColor::ButtonActive] = to_rgb01([182, 182, 182, 140]); 364 | style[imgui::StyleColor::PopupBg] = to_rgb01([0, 0, 0, 230]); 365 | style[imgui::StyleColor::TextSelectedBg] = to_rgb01([10, 23, 18, 180]); 366 | style[imgui::StyleColor::FrameBg] = to_rgb01([70, 70, 70, 30]); 367 | style[imgui::StyleColor::FrameBgHovered] = to_rgb01([70, 70, 70, 70]); 368 | style[imgui::StyleColor::FrameBgActive] = to_rgb01([70, 70, 70, 50]); 369 | style[imgui::StyleColor::MenuBarBg] = to_rgb01([70, 70, 70, 30]); 370 | 371 | // Setup dear imgui wgpu renderer 372 | let clear_color = wgpu::Color { 373 | r: 0.03, 374 | g: 0.03, 375 | b: 0.03, 376 | a: 1.0, 377 | }; 378 | let mut renderer = Renderer::new( 379 | &mut imgui, 380 | &device, 381 | &mut queue, 382 | sc_desc.format, 383 | Some(clear_color), 384 | ); 385 | 386 | let mut last_frame = Instant::now(); 387 | 388 | let mut screen = ScreenBuffer::new(&mut renderer, &device); 389 | let mut state_win = StateWindow::new(&mut renderer, &device); 390 | 391 | let mut last_cursor = None; 392 | 393 | // Event loop 394 | event_loop.run(move |event, _, control_flow| { 395 | let self_mut = Rc::get_mut(&mut self).unwrap(); 396 | 397 | *control_flow = if cfg!(feature = "metal-auto-capture") { 398 | ControlFlow::Exit 399 | } else { 400 | ControlFlow::Poll 401 | }; 402 | match event { 403 | Event::WindowEvent { 404 | event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, 405 | .. 406 | } => { 407 | hidpi_factor = scale_factor; 408 | } 409 | Event::WindowEvent { 410 | event: WindowEvent::Resized(_), 411 | .. 412 | } => { 413 | size = window.inner_size(); 414 | 415 | sc_desc = wgpu::SwapChainDescriptor { 416 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 417 | format: wgpu::TextureFormat::Bgra8Unorm, 418 | width: size.width as u32, 419 | height: size.height as u32, 420 | present_mode: wgpu::PresentMode::Mailbox, 421 | }; 422 | 423 | swap_chain = device.create_swap_chain(&surface, &sc_desc); 424 | } 425 | Event::WindowEvent { 426 | event: 427 | WindowEvent::KeyboardInput { 428 | input: 429 | KeyboardInput { 430 | virtual_keycode: Some(VirtualKeyCode::Escape), 431 | state: ElementState::Pressed, 432 | .. 433 | }, 434 | .. 435 | }, 436 | .. 437 | } 438 | | Event::WindowEvent { 439 | event: WindowEvent::CloseRequested, 440 | .. 441 | } => { 442 | *control_flow = ControlFlow::Exit; 443 | } 444 | Event::WindowEvent { 445 | event: 446 | WindowEvent::KeyboardInput { 447 | input: 448 | KeyboardInput { 449 | virtual_keycode: Some(virtual_keycode), 450 | state, 451 | .. 452 | }, 453 | .. 454 | }, 455 | .. 456 | } => { 457 | self_mut.set_key_state(virtual_keycode, state == ElementState::Pressed); 458 | } 459 | Event::MainEventsCleared => { 460 | window.request_redraw(); 461 | } 462 | Event::RedrawEventsCleared => { 463 | last_frame = imgui.io_mut().update_delta_time(last_frame); 464 | 465 | let frame = match swap_chain.get_next_texture() { 466 | Ok(frame) => frame, 467 | Err(e) => { 468 | eprintln!("dropped frame: {:?}", e); 469 | return; 470 | } 471 | }; 472 | platform 473 | .prepare_frame(imgui.io_mut(), &window) 474 | .expect("Failed to prepare frame"); 475 | let ui = imgui.frame(); 476 | 477 | // Run emulator update 478 | self_mut.emulator.update(ui.io().delta_time); 479 | 480 | { 481 | // Read and update screen buffer if changed: 482 | let mut ppu = self_mut.emulator.ppu.borrow_mut(); 483 | if ppu.screen.complete { 484 | ppu.screen.complete = false; 485 | screen.update(&ppu, &mut renderer, &device, &mut queue); 486 | } 487 | 488 | // Read and update state textures 489 | state_win.update(&mut ppu, &mut renderer, &device, &mut queue); 490 | } 491 | 492 | // Draw actual app UI 493 | self_mut.draw_ui(&ui); 494 | // Draw screen window 495 | screen.draw_ui(&ui); 496 | // Draw state window 497 | state_win.draw_ui(&ui, &mut self_mut.emulator); 498 | 499 | let mut encoder: wgpu::CommandEncoder = device 500 | .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); 501 | 502 | if last_cursor != Some(ui.mouse_cursor()) { 503 | last_cursor = Some(ui.mouse_cursor()); 504 | platform.prepare_render(&ui, &window); 505 | } 506 | renderer 507 | .render(ui.render(), &mut device, &mut encoder, &frame.view) 508 | .expect("Rendering failed"); 509 | 510 | queue.submit(&[encoder.finish()]); 511 | } 512 | _ => (), 513 | } 514 | 515 | platform.handle_event(imgui.io_mut(), &window, &event); 516 | }); 517 | } 518 | } 519 | -------------------------------------------------------------------------------- /src/imgui_wgpu/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Steven Wittens 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /src/imgui_wgpu/imgui.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(set = 1, binding = 0) uniform texture2D u_Texture; 4 | layout(set = 1, binding = 1) uniform sampler u_Sampler; 5 | 6 | layout(location = 0) in vec2 v_UV; 7 | layout(location = 1) in vec4 v_Color; 8 | 9 | layout(location = 0) out vec4 o_Target; 10 | 11 | void main() { 12 | o_Target = v_Color * texture(sampler2D(u_Texture, u_Sampler), v_UV); 13 | } 14 | -------------------------------------------------------------------------------- /src/imgui_wgpu/imgui.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoselov-ab/nes-rust/cae249307f320ec9b6cf1ac96806b1fe6247ede8/src/imgui_wgpu/imgui.frag.spv -------------------------------------------------------------------------------- /src/imgui_wgpu/imgui.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(set = 0, binding = 0) uniform View { 4 | mat4 u_Matrix; 5 | }; 6 | 7 | layout(location = 0) in vec2 a_Pos; 8 | layout(location = 1) in vec2 a_UV; 9 | layout(location = 2) in uint a_Color; 10 | 11 | layout(location = 0) out vec2 v_UV; 12 | layout(location = 1) out vec4 v_Color; 13 | 14 | // Built-in: 15 | // vec4 gl_Position 16 | 17 | void main() { 18 | v_UV = a_UV; 19 | v_Color = vec4(a_Color & 0xFF, (a_Color >> 8) & 0xFF, (a_Color >> 16) & 0xFF, (a_Color >> 24) & 0xFF) / 255.0; 20 | gl_Position = u_Matrix * vec4(a_Pos.xy, 0.0, 1.0); 21 | } 22 | -------------------------------------------------------------------------------- /src/imgui_wgpu/imgui.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novoselov-ab/nes-rust/cae249307f320ec9b6cf1ac96806b1fe6247ede8/src/imgui_wgpu/imgui.vert.spv -------------------------------------------------------------------------------- /src/imgui_wgpu/mod.rs: -------------------------------------------------------------------------------- 1 | use imgui::{ 2 | Context, DrawCmd::Elements, DrawData, DrawIdx, DrawList, DrawVert, TextureId, Textures, 3 | }; 4 | use std::mem::size_of; 5 | use wgpu::*; 6 | 7 | pub type RendererResult = Result; 8 | 9 | // TODO: This value may change 10 | // https://github.com/gfx-rs/wgpu-rs/issues/199 11 | const WHOLE_BUFFER: u64 = 0; 12 | 13 | #[derive(Clone, Debug)] 14 | pub enum RendererError { 15 | BadTexture(TextureId), 16 | } 17 | 18 | #[allow(dead_code)] 19 | enum ShaderStage { 20 | Vertex, 21 | Fragment, 22 | Compute, 23 | } 24 | 25 | #[cfg(feature = "glsl-to-spirv")] 26 | struct Shaders; 27 | 28 | #[cfg(feature = "glsl-to-spirv")] 29 | impl Shaders { 30 | fn compile_glsl(code: &str, stage: ShaderStage) -> Vec { 31 | let ty = match stage { 32 | ShaderStage::Vertex => glsl_to_spirv::ShaderType::Vertex, 33 | ShaderStage::Fragment => glsl_to_spirv::ShaderType::Fragment, 34 | ShaderStage::Compute => glsl_to_spirv::ShaderType::Compute, 35 | }; 36 | 37 | read_spirv(glsl_to_spirv::compile(&code, ty).unwrap()).unwrap() 38 | } 39 | 40 | fn get_program_code() -> (&'static str, &'static str) { 41 | (include_str!("imgui.vert"), include_str!("imgui.frag")) 42 | } 43 | } 44 | 45 | /// A container for a bindable texture to be used internally. 46 | pub struct Texture { 47 | bind_group: BindGroup, 48 | texture: wgpu::Texture, 49 | } 50 | 51 | impl Texture { 52 | /// Creates a new imgui texture from a wgpu texture. 53 | pub fn new(texture: wgpu::Texture, layout: &BindGroupLayout, device: &Device) -> Self { 54 | // Extract the texture view. 55 | let view = texture.create_default_view(); 56 | 57 | // Create the texture sampler. 58 | let sampler = device.create_sampler(&SamplerDescriptor { 59 | address_mode_u: AddressMode::ClampToEdge, 60 | address_mode_v: AddressMode::ClampToEdge, 61 | address_mode_w: AddressMode::ClampToEdge, 62 | mag_filter: FilterMode::Nearest, 63 | min_filter: FilterMode::Nearest, 64 | mipmap_filter: FilterMode::Linear, 65 | lod_min_clamp: -100.0, 66 | lod_max_clamp: 100.0, 67 | compare: CompareFunction::Always, 68 | }); 69 | 70 | // Create the texture bind group from the layout. 71 | let bind_group = device.create_bind_group(&BindGroupDescriptor { 72 | label: None, 73 | layout, 74 | bindings: &[ 75 | Binding { 76 | binding: 0, 77 | resource: BindingResource::TextureView(&view), 78 | }, 79 | Binding { 80 | binding: 1, 81 | resource: BindingResource::Sampler(&sampler), 82 | }, 83 | ], 84 | }); 85 | 86 | Texture { 87 | bind_group, 88 | texture, 89 | } 90 | } 91 | } 92 | 93 | #[allow(dead_code)] 94 | pub struct Renderer { 95 | pipeline: RenderPipeline, 96 | uniform_buffer: Buffer, 97 | uniform_bind_group: BindGroup, 98 | textures: Textures, 99 | texture_layout: BindGroupLayout, 100 | clear_color: Option, 101 | index_buffers: Vec, 102 | vertex_buffers: Vec, 103 | } 104 | 105 | impl Renderer { 106 | /// Create a new imgui wgpu renderer with newly compiled shaders. 107 | #[cfg(feature = "glsl-to-spirv")] 108 | pub fn new_glsl( 109 | imgui: &mut Context, 110 | device: &Device, 111 | queue: &mut Queue, 112 | format: TextureFormat, 113 | clear_color: Option, 114 | ) -> Renderer { 115 | let (vs_code, fs_code) = Shaders::get_program_code(); 116 | let vs_raw = Shaders::compile_glsl(vs_code, ShaderStage::Vertex); 117 | let fs_raw = Shaders::compile_glsl(fs_code, ShaderStage::Fragment); 118 | Self::new_impl(imgui, device, queue, format, clear_color, vs_raw, fs_raw) 119 | } 120 | 121 | /// Create a new imgui wgpu renderer, using prebuilt spirv shaders. 122 | pub fn new( 123 | imgui: &mut Context, 124 | device: &Device, 125 | queue: &mut Queue, 126 | format: TextureFormat, 127 | clear_color: Option, 128 | ) -> Renderer { 129 | let vs_bytes = include_bytes!("imgui.vert.spv"); 130 | let fs_bytes = include_bytes!("imgui.frag.spv"); 131 | 132 | fn compile(shader: &[u8]) -> Vec { 133 | let mut words = vec![]; 134 | for bytes4 in shader.chunks(4) { 135 | words.push(u32::from_le_bytes([ 136 | bytes4[0], bytes4[1], bytes4[2], bytes4[3], 137 | ])); 138 | } 139 | words 140 | } 141 | 142 | Self::new_impl( 143 | imgui, 144 | device, 145 | queue, 146 | format, 147 | clear_color, 148 | compile(vs_bytes), 149 | compile(fs_bytes), 150 | ) 151 | } 152 | 153 | /// Create an entirely new imgui wgpu renderer. 154 | fn new_impl( 155 | imgui: &mut Context, 156 | device: &Device, 157 | queue: &mut Queue, 158 | format: TextureFormat, 159 | clear_color: Option, 160 | vs_raw: Vec, 161 | fs_raw: Vec, 162 | ) -> Renderer { 163 | // Load shaders. 164 | let vs_module = device.create_shader_module(&vs_raw); 165 | let fs_module = device.create_shader_module(&fs_raw); 166 | 167 | // Create the uniform matrix buffer. 168 | let size = 64; 169 | let uniform_buffer = device.create_buffer(&BufferDescriptor { 170 | label: None, 171 | size, 172 | usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST, 173 | }); 174 | 175 | // Create the uniform matrix buffer bind group layout. 176 | let uniform_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { 177 | label: None, 178 | bindings: &[BindGroupLayoutEntry { 179 | binding: 0, 180 | visibility: wgpu::ShaderStage::VERTEX, 181 | ty: BindingType::UniformBuffer { dynamic: false }, 182 | }], 183 | }); 184 | 185 | // Create the uniform matrix buffer bind group. 186 | let uniform_bind_group = device.create_bind_group(&BindGroupDescriptor { 187 | label: None, 188 | layout: &uniform_layout, 189 | bindings: &[Binding { 190 | binding: 0, 191 | resource: BindingResource::Buffer { 192 | buffer: &uniform_buffer, 193 | range: 0..size, 194 | }, 195 | }], 196 | }); 197 | 198 | // Create the texture layout for further usage. 199 | let texture_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { 200 | label: None, 201 | bindings: &[ 202 | BindGroupLayoutEntry { 203 | binding: 0, 204 | visibility: wgpu::ShaderStage::FRAGMENT, 205 | ty: BindingType::SampledTexture { 206 | multisampled: false, 207 | component_type: TextureComponentType::Float, 208 | dimension: TextureViewDimension::D2, 209 | }, 210 | }, 211 | BindGroupLayoutEntry { 212 | binding: 1, 213 | visibility: wgpu::ShaderStage::FRAGMENT, 214 | ty: BindingType::Sampler { comparison: false }, 215 | }, 216 | ], 217 | }); 218 | 219 | // Create the render pipeline layout. 220 | let pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { 221 | bind_group_layouts: &[&uniform_layout, &texture_layout], 222 | }); 223 | 224 | // Create the render pipeline. 225 | let pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { 226 | layout: &pipeline_layout, 227 | vertex_stage: ProgrammableStageDescriptor { 228 | module: &vs_module, 229 | entry_point: "main", 230 | }, 231 | fragment_stage: Some(ProgrammableStageDescriptor { 232 | module: &fs_module, 233 | entry_point: "main", 234 | }), 235 | rasterization_state: Some(RasterizationStateDescriptor { 236 | front_face: FrontFace::Cw, 237 | cull_mode: CullMode::None, 238 | depth_bias: 0, 239 | depth_bias_slope_scale: 0.0, 240 | depth_bias_clamp: 0.0, 241 | }), 242 | primitive_topology: PrimitiveTopology::TriangleList, 243 | color_states: &[ColorStateDescriptor { 244 | format, 245 | color_blend: BlendDescriptor { 246 | src_factor: BlendFactor::SrcAlpha, 247 | dst_factor: BlendFactor::OneMinusSrcAlpha, 248 | operation: BlendOperation::Add, 249 | }, 250 | alpha_blend: BlendDescriptor { 251 | src_factor: BlendFactor::OneMinusDstAlpha, 252 | dst_factor: BlendFactor::One, 253 | operation: BlendOperation::Add, 254 | }, 255 | write_mask: ColorWrite::ALL, 256 | }], 257 | depth_stencil_state: None, 258 | vertex_state: VertexStateDescriptor { 259 | index_format: IndexFormat::Uint16, 260 | vertex_buffers: &[VertexBufferDescriptor { 261 | stride: size_of::() as BufferAddress, 262 | step_mode: InputStepMode::Vertex, 263 | attributes: &[ 264 | VertexAttributeDescriptor { 265 | format: VertexFormat::Float2, 266 | shader_location: 0, 267 | offset: 0, 268 | }, 269 | VertexAttributeDescriptor { 270 | format: VertexFormat::Float2, 271 | shader_location: 1, 272 | offset: 8, 273 | }, 274 | VertexAttributeDescriptor { 275 | format: VertexFormat::Uint, 276 | shader_location: 2, 277 | offset: 16, 278 | }, 279 | ], 280 | }], 281 | }, 282 | sample_count: 1, 283 | sample_mask: !0, 284 | alpha_to_coverage_enabled: false, 285 | }); 286 | 287 | let mut renderer = Renderer { 288 | pipeline, 289 | uniform_buffer, 290 | uniform_bind_group, 291 | textures: Textures::new(), 292 | texture_layout, 293 | clear_color, 294 | vertex_buffers: vec![], 295 | index_buffers: vec![], 296 | }; 297 | 298 | // Immediately load the fon texture to the GPU. 299 | renderer.reload_font_texture(imgui, device, queue); 300 | 301 | renderer 302 | } 303 | 304 | /// Render the current imgui frame. 305 | pub fn render<'r>( 306 | &'r mut self, 307 | draw_data: &DrawData, 308 | device: &Device, 309 | encoder: &'r mut CommandEncoder, 310 | view: &TextureView, 311 | ) -> RendererResult<()> { 312 | let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0]; 313 | let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1]; 314 | 315 | // If the render area is <= 0, exit here and now. 316 | if !(fb_width > 0.0 && fb_height > 0.0) { 317 | return Ok(()); 318 | } 319 | 320 | let width = draw_data.display_size[0]; 321 | let height = draw_data.display_size[1]; 322 | 323 | // Create and update the transform matrix for the current frame. 324 | // This is required to adapt to vulkan coordinates. 325 | // let matrix = [ 326 | // [2.0 / width, 0.0, 0.0, 0.0], 327 | // [0.0, 2.0 / height as f32, 0.0, 0.0], 328 | // [0.0, 0.0, -1.0, 0.0], 329 | // [-1.0, -1.0, 0.0, 1.0], 330 | // ]; 331 | let matrix = [ 332 | [2.0 / width, 0.0, 0.0, 0.0], 333 | [0.0, 2.0 / -height as f32, 0.0, 0.0], 334 | [0.0, 0.0, 1.0, 0.0], 335 | [-1.0, 1.0, 0.0, 1.0], 336 | ]; 337 | self.update_uniform_buffer(device, encoder, &matrix); 338 | 339 | // Start a new renderpass and prepare it properly. 340 | let mut rpass = encoder.begin_render_pass(&RenderPassDescriptor { 341 | color_attachments: &[RenderPassColorAttachmentDescriptor { 342 | attachment: &view, 343 | resolve_target: None, 344 | load_op: match self.clear_color { 345 | Some(_) => LoadOp::Clear, 346 | _ => LoadOp::Load, 347 | }, 348 | store_op: StoreOp::Store, 349 | clear_color: self.clear_color.unwrap_or(Color { 350 | r: 0.0, 351 | g: 0.0, 352 | b: 0.0, 353 | a: 1.0, 354 | }), 355 | }], 356 | depth_stencil_attachment: None, 357 | }); 358 | rpass.set_pipeline(&self.pipeline); 359 | rpass.set_bind_group(0, &self.uniform_bind_group, &[]); 360 | 361 | self.vertex_buffers.clear(); 362 | self.index_buffers.clear(); 363 | 364 | for draw_list in draw_data.draw_lists() { 365 | self.vertex_buffers 366 | .push(self.upload_vertex_buffer(device, draw_list.vtx_buffer())); 367 | self.index_buffers 368 | .push(self.upload_index_buffer(device, draw_list.idx_buffer())); 369 | } 370 | 371 | // Execute all the imgui render work. 372 | for (draw_list_buffers_index, draw_list) in draw_data.draw_lists().enumerate() { 373 | self.render_draw_list( 374 | &mut rpass, 375 | &draw_list, 376 | draw_data.display_pos, 377 | draw_data.framebuffer_scale, 378 | draw_list_buffers_index, 379 | )?; 380 | } 381 | 382 | Ok(()) 383 | } 384 | 385 | /// Render a given `DrawList` from imgui onto a wgpu frame. 386 | fn render_draw_list<'render>( 387 | &'render self, 388 | rpass: &mut RenderPass<'render>, 389 | draw_list: &DrawList, 390 | clip_off: [f32; 2], 391 | clip_scale: [f32; 2], 392 | draw_list_buffers_index: usize, 393 | ) -> RendererResult<()> { 394 | let mut start = 0; 395 | 396 | let index_buffer = &self.index_buffers[draw_list_buffers_index]; 397 | let vertex_buffer = &self.vertex_buffers[draw_list_buffers_index]; 398 | 399 | // Make sure the current buffers are attached to the render pass. 400 | rpass.set_index_buffer(&index_buffer, 0, WHOLE_BUFFER); 401 | rpass.set_vertex_buffer(0, &vertex_buffer, 0, WHOLE_BUFFER); 402 | 403 | for cmd in draw_list.commands() { 404 | match cmd { 405 | Elements { count, cmd_params } => { 406 | let clip_rect = [ 407 | (cmd_params.clip_rect[0] - clip_off[0]) * clip_scale[0], 408 | (cmd_params.clip_rect[1] - clip_off[1]) * clip_scale[1], 409 | (cmd_params.clip_rect[2] - clip_off[0]) * clip_scale[0], 410 | (cmd_params.clip_rect[3] - clip_off[1]) * clip_scale[1], 411 | ]; 412 | 413 | // Set the current texture bind group on the renderpass. 414 | let texture_id = cmd_params.texture_id.into(); 415 | let tex = self 416 | .textures 417 | .get(texture_id) 418 | .ok_or_else(|| RendererError::BadTexture(texture_id))?; 419 | rpass.set_bind_group(1, &tex.bind_group, &[]); 420 | 421 | // Set scissors on the renderpass. 422 | let scissors = ( 423 | clip_rect[0].max(0.0).floor() as u32, 424 | clip_rect[1].max(0.0).floor() as u32, 425 | (clip_rect[2] - clip_rect[0]).abs().ceil() as u32, 426 | (clip_rect[3] - clip_rect[1]).abs().ceil() as u32, 427 | ); 428 | rpass.set_scissor_rect(scissors.0, scissors.1, scissors.2, scissors.3); 429 | 430 | // Draw the current batch of vertices with the renderpass. 431 | let end = start + count as u32; 432 | rpass.draw_indexed(start..end, 0, 0..1); 433 | start = end; 434 | } 435 | _ => {} 436 | } 437 | } 438 | Ok(()) 439 | } 440 | 441 | /// Updates the current uniform buffer containing the transform matrix. 442 | fn update_uniform_buffer( 443 | &mut self, 444 | device: &Device, 445 | encoder: &mut CommandEncoder, 446 | matrix: &[[f32; 4]; 4], 447 | ) { 448 | let data = as_byte_slice(matrix); 449 | // Create a new buffer. 450 | let buffer = device.create_buffer_with_data(data, BufferUsage::COPY_SRC); 451 | 452 | // Copy the new buffer to the real buffer. 453 | encoder.copy_buffer_to_buffer(&buffer, 0, &self.uniform_buffer, 0, 64); 454 | } 455 | 456 | /// Upload the vertex buffer to the gPU. 457 | fn upload_vertex_buffer(&self, device: &Device, vertices: &[DrawVert]) -> Buffer { 458 | let data = as_byte_slice(&vertices); 459 | device.create_buffer_with_data(data, BufferUsage::VERTEX) 460 | } 461 | 462 | /// Upload the index buffer to the GPU. 463 | fn upload_index_buffer(&self, device: &Device, indices: &[DrawIdx]) -> Buffer { 464 | let data = as_byte_slice(&indices); 465 | device.create_buffer_with_data(data, BufferUsage::INDEX) 466 | } 467 | 468 | /// Updates the texture on the GPU corresponding to the current imgui font atlas. 469 | /// 470 | /// This has to be called after loading a font. 471 | pub fn reload_font_texture(&mut self, imgui: &mut Context, device: &Device, queue: &mut Queue) { 472 | let mut atlas = imgui.fonts(); 473 | let handle = atlas.build_rgba32_texture(); 474 | let font_texture_id = 475 | self.upload_texture(device, queue, &handle.data, handle.width, handle.height); 476 | 477 | atlas.tex_id = font_texture_id; 478 | } 479 | 480 | /// Creates and uploads a new wgpu texture made from the imgui font atlas. 481 | pub fn update_texture( 482 | &mut self, 483 | id: TextureId, 484 | device: &Device, 485 | queue: &mut Queue, 486 | data: &[u8], 487 | width: u32, 488 | height: u32, 489 | ) -> Option { 490 | // Create the wgpu texture. 491 | //let texture = self.textures.get(id)?.texture; 492 | 493 | // Upload the actual data to a wgpu buffer. 494 | let bytes = data.len(); 495 | let buffer = device.create_buffer_with_data(data, BufferUsage::COPY_SRC); 496 | 497 | // Make sure we have an active encoder. 498 | let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor { label: None }); 499 | 500 | // Schedule a copy from the buffer to the texture. 501 | encoder.copy_buffer_to_texture( 502 | BufferCopyView { 503 | buffer: &buffer, 504 | offset: 0, 505 | bytes_per_row: bytes as u32 / height, 506 | rows_per_image: height, 507 | }, 508 | TextureCopyView { 509 | texture: &self.textures.get(id)?.texture, 510 | mip_level: 0, 511 | array_layer: 0, 512 | origin: Origin3d { x: 0, y: 0, z: 0 }, 513 | }, 514 | Extent3d { 515 | width, 516 | height, 517 | depth: 1, 518 | }, 519 | ); 520 | 521 | // Resolve the actual copy process. 522 | queue.submit(&[encoder.finish()]); 523 | 524 | Some(true) 525 | } 526 | 527 | /// Creates a new wgpu texture made from the imgui font atlas. 528 | pub fn create_texture(&mut self, device: &Device, width: u32, height: u32) -> TextureId { 529 | // Create the wgpu texture. 530 | let texture = device.create_texture(&TextureDescriptor { 531 | label: None, 532 | size: Extent3d { 533 | width, 534 | height, 535 | depth: 1, 536 | }, 537 | array_layer_count: 1, 538 | mip_level_count: 1, 539 | sample_count: 1, 540 | dimension: TextureDimension::D2, 541 | format: TextureFormat::Rgba8Unorm, 542 | usage: TextureUsage::SAMPLED | TextureUsage::COPY_DST, 543 | }); 544 | 545 | let texture = Texture::new(texture, &self.texture_layout, device); 546 | self.textures.insert(texture) 547 | } 548 | 549 | /// Creates and uploads a new wgpu texture made from the imgui font atlas. 550 | pub fn upload_texture( 551 | &mut self, 552 | device: &Device, 553 | queue: &mut Queue, 554 | data: &[u8], 555 | width: u32, 556 | height: u32, 557 | ) -> TextureId { 558 | // Create the wgpu texture. 559 | let id = self.create_texture(device, width, height); 560 | self.update_texture(id, device, queue, data, width, height); 561 | id 562 | } 563 | } 564 | 565 | fn as_byte_slice(slice: &[T]) -> &[u8] { 566 | let len = slice.len() * std::mem::size_of::(); 567 | let ptr = slice.as_ptr() as *const u8; 568 | unsafe { std::slice::from_raw_parts(ptr, len) } 569 | } 570 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod imgui_wgpu; 3 | mod nes; 4 | 5 | use app::NESApp; 6 | use std::rc::Rc; 7 | 8 | fn main() { 9 | let app = Rc::new(NESApp::new()); 10 | app.run() 11 | } 12 | -------------------------------------------------------------------------------- /src/nes/bus.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::ops::Range; 3 | use std::rc::Rc; 4 | 5 | struct DeviceConnection { 6 | device: Rc>, 7 | addr_range: Range, 8 | } 9 | 10 | pub struct Bus { 11 | connections: Vec, 12 | } 13 | 14 | pub trait CpuBusDevice { 15 | fn get_addr_range(&self) -> &Range; 16 | 17 | fn cpu_write(&mut self, addr: u16, data: u8); 18 | fn cpu_read(&mut self, addr: u16) -> u8; 19 | } 20 | 21 | impl Bus { 22 | pub fn new() -> Self { 23 | Bus { 24 | connections: vec![], 25 | } 26 | } 27 | 28 | pub fn cpu_write(&mut self, addr: u16, data: u8) { 29 | for connection in &mut self.connections { 30 | if connection.addr_range.contains(&addr) { 31 | let mut device = connection.device.borrow_mut(); 32 | device.cpu_write(addr, data); 33 | return; 34 | } 35 | } 36 | 37 | //panic!("no device with range: {} to write to.", addr); 38 | } 39 | 40 | pub fn cpu_read(&mut self, addr: u16) -> u8 { 41 | for connection in &mut self.connections { 42 | if connection.addr_range.contains(&addr) { 43 | let mut device = connection.device.borrow_mut(); 44 | return device.cpu_read(addr); 45 | } 46 | } 47 | //panic!("no device with range: {} to read from.", addr); 48 | 49 | 0 50 | } 51 | 52 | pub fn connect(&mut self, device: Rc>) { 53 | let addr_range = device.borrow_mut().get_addr_range().clone(); 54 | self.connections.push(DeviceConnection { 55 | device: device, 56 | addr_range: addr_range, 57 | }); 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use super::super::ram::Ram; 64 | use super::*; 65 | 66 | #[test] 67 | fn bus_devices() { 68 | let mut b = Bus::new(); 69 | 70 | let r1 = Rc::new(RefCell::new(Ram::new())); 71 | b.connect(r1.clone()); 72 | 73 | b.cpu_write(25, 16); 74 | 75 | assert_eq!(r1.borrow_mut().bytes[5], 0); 76 | assert_eq!(r1.borrow_mut().bytes[25], 16); 77 | 78 | assert_eq!(b.cpu_read(25), 16); 79 | assert_eq!(b.cpu_read(24), 0); 80 | assert_eq!(b.cpu_read(5), 0); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/nes/cartridge.rs: -------------------------------------------------------------------------------- 1 | use super::bus::CpuBusDevice; 2 | use std::fs; 3 | use std::ops::Range; 4 | use std::path::PathBuf; 5 | 6 | use super::mappers::{Mapper, Mapper0, Mapper3}; 7 | 8 | pub struct Cartridge { 9 | prg_rom: Vec, 10 | chr_rom: Vec, 11 | prg_ram: Vec, 12 | mapper: Box, 13 | vertical_mirror: bool, 14 | } 15 | 16 | impl CpuBusDevice for Cartridge { 17 | fn get_addr_range(&self) -> &Range { 18 | &(0x8000..0xFFFF) 19 | } 20 | 21 | fn cpu_write(&mut self, addr: u16, data: u8) { 22 | self.mapper.map_write(addr, data); 23 | } 24 | 25 | fn cpu_read(&mut self, addr: u16) -> u8 { 26 | let mapped_addr = self.mapper.map_read(addr); 27 | self.prg_rom[(mapped_addr as usize)] 28 | } 29 | } 30 | 31 | impl Cartridge { 32 | pub fn new() -> Self { 33 | Cartridge { 34 | prg_rom: vec![], 35 | chr_rom: vec![], 36 | prg_ram: vec![], 37 | mapper: Box::new(Mapper0::new(0)), 38 | vertical_mirror: false, 39 | } 40 | } 41 | 42 | pub fn ppu_write(&mut self, addr: u16, data: u8) { 43 | let mapped_addr = self.mapper.map_ppu_write(addr); 44 | self.chr_rom[(mapped_addr as usize)] = data; 45 | } 46 | 47 | pub fn ppu_read(&mut self, addr: u16) -> u8 { 48 | let mapped_addr = self.mapper.map_ppu_read(addr) as usize; 49 | if mapped_addr >= self.chr_rom.len() { 50 | return 0; 51 | } 52 | self.chr_rom[mapped_addr] 53 | } 54 | 55 | pub fn is_vertical_mirror(&self) -> bool { 56 | // TODO: add mapper? 57 | self.vertical_mirror 58 | } 59 | 60 | pub fn load_from_file(&mut self, romfile: &PathBuf) { 61 | // Load ROM from file 62 | let contents = match fs::read(romfile) { 63 | Err(e) => { 64 | println!("Can't read file: '{0}'. Error: {1}", romfile.display(), e); 65 | return; 66 | } 67 | Ok(f) => f, 68 | }; 69 | 70 | let mut next = 0; 71 | 72 | ////////////////////////////////////////////// 73 | // 16 byte Header 74 | ////////////////////////////////////////////// 75 | 76 | // 00h File ID ("NES",1Ah) (aka 4Eh,45h,53h,1Ah) 77 | if contents[0..4] != ['N' as u8, 'E' as u8, 'S' as u8, 0x1A] { 78 | println!("Wrong file ID in nes file: '{0}'.", romfile.display()); 79 | return; 80 | } 81 | 82 | // 04h Number of 16K PRG-ROM pages 83 | let rom_pages = contents[4]; 84 | 85 | // 05h Number of 8K CHR-ROM pages (00h=None / VRAM) 86 | let chr_pages = contents[5]; 87 | 88 | // 06h Cartridge Type LSB 89 | // Bit7-4 Mapper Number (lower 4bits) 90 | // Bit3 1=Four-screen VRAM layout 91 | // Bit2 1=512-byte trainer/patch at 7000h-71FFh 92 | // Bit1 1=Battery-backed SRAM at 6000h-7FFFh, set only if battery-backed 93 | // Bit0 0=Horizontal mirroring, 1=Vertical mirroring 94 | let type_lsb = contents[6]; 95 | let has_trainer = (type_lsb & (1 << 2)) != 0; 96 | self.vertical_mirror = (type_lsb & (1 << 0)) != 0; 97 | 98 | // 07h Cartridge Type MSB (ignore this and further bytes if Byte 0Fh nonzero) 99 | // Bit7-4 Mapper Number (upper 4bits) 100 | // Bit3-2 Reserved (zero) 101 | // Bit1 1=PC10 game (arcade machine with additional 8K Z80-ROM) (*) 102 | // Bit0 1=VS Unisystem game (arcade machine with different palette) 103 | let type_msb = contents[7]; 104 | 105 | // Read mapper 106 | let mapper_number = ((type_lsb >> 4) & 0xf) | (type_msb & 0xf0); 107 | 108 | // 08h Number of 8K RAM (SRAM?) pages (usually 00h=None-or-not-specified) 109 | let ram_pages = contents[8]; 110 | 111 | next += 16; // Header size 112 | 113 | ////////////////////////////////////////////// 114 | // 512 byte Trainer 115 | ////////////////////////////////////////////// 116 | 117 | if has_trainer { 118 | next += 512; 119 | } 120 | 121 | ////////////////////////////////////////////// 122 | // N*16K PRG-ROM 123 | ////////////////////////////////////////////// 124 | { 125 | let rom_size = (rom_pages as usize) * 0x4000; 126 | self.prg_rom = contents[next..next + rom_size].to_vec(); 127 | println!("rom_size: {:?}", rom_size); 128 | next += rom_size; 129 | } 130 | 131 | ////////////////////////////////////////////// 132 | // N*8K CHR-ROM 133 | ////////////////////////////////////////////// 134 | { 135 | let chr_size = (chr_pages as usize) * 0x2000; 136 | self.chr_rom = contents[next..next + chr_size].to_vec(); 137 | println!("chr_size: {:?}", chr_size); 138 | //next += chr_size; 139 | } 140 | 141 | ////////////////////////////////////////////// 142 | // N*8K PRG-RAM 143 | ////////////////////////////////////////////// 144 | { 145 | let ram_size = (ram_pages as usize) * 0x204C; 146 | self.prg_ram = vec![0; ram_size]; 147 | } 148 | 149 | // Load mapper 150 | match mapper_number { 151 | 0 => self.mapper = Box::new(Mapper0::new(rom_pages)), 152 | 3 => self.mapper = Box::new(Mapper3::new(rom_pages)), 153 | _ => { 154 | println!("Unsupported mapper: {:?}", mapper_number); 155 | return; 156 | } 157 | } 158 | 159 | /* 160 | iNES Format (.NES) 161 | The overall file structure is, in following order: 162 | 16 byte Header 163 | 512 byte Trainer ;-if any, see Byte 6, Bit2, mainly FFE games 164 | N*16K PRG-ROM ;-see Byte 4 165 | N*8K CHR-ROM ;-if any, see Byte 5 166 | 8K (*) PC10 INST-ROM ;-if any, see Byte 7, Bit1 167 | 16 byte (*) PC10 PROM Data ;-if any, see Byte 7, Bit1 ;\required, but 168 | 16 byte (*) PC10 PROM CounterOut;-if any, see Byte 7, Bit1 ;/often missing 169 | 128 byte (*) Title ;-if any (rarely used) 170 | Items marked as (*) are regulary used, but not offical part of the format. 171 | Many PC10 files declare Z80-ROM as additional VROM bank (instead Byte7/Bit1). 172 | */ 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/nes/controller.rs: -------------------------------------------------------------------------------- 1 | use super::bus::CpuBusDevice; 2 | use std::ops::Range; 3 | 4 | // Support only one for now 5 | 6 | pub struct Controller { 7 | pub input: u8, 8 | pub state: u8, 9 | pub num: u16, 10 | } 11 | 12 | impl CpuBusDevice for Controller { 13 | fn get_addr_range(&self) -> &Range { 14 | if self.num == 0 { 15 | &(0x4016..0x4017) 16 | } else { 17 | &(0x4017..0x4018) 18 | } 19 | } 20 | 21 | fn cpu_write(&mut self, _: u16, _: u8) { 22 | self.state = self.input; 23 | } 24 | 25 | fn cpu_read(&mut self, _: u16) -> u8 { 26 | let data = ((self.state & 0x80) > 0) as u8; 27 | self.state <<= 1; 28 | data 29 | } 30 | } 31 | 32 | impl Controller { 33 | pub fn new(num: u16) -> Self { 34 | Controller { 35 | input: 0, 36 | state: 0, 37 | num: num, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/nes/cpu.rs: -------------------------------------------------------------------------------- 1 | use super::bus::Bus; 2 | 3 | #[derive(Default, Clone)] 4 | #[allow(non_snake_case)] 5 | pub struct Flags { 6 | pub C: bool, // Carry Flag 7 | pub Z: bool, // Zero Flag 8 | pub I: bool, // Interrupt Disable 9 | pub D: bool, // Decimal Mode 10 | pub B: bool, // Break Command 11 | pub U: bool, // Unused 12 | pub O: bool, // Overflow Flag 13 | pub N: bool, // Negative Flags 14 | } 15 | #[allow(non_snake_case)] 16 | pub struct Cpu { 17 | pub bus: Bus, 18 | pub PC: u16, 19 | pub SP: u8, // ? 20 | pub A: u8, 21 | pub X: u8, 22 | pub Y: u8, 23 | pub flags: Flags, 24 | pub total_cycles: usize, 25 | pub cycles: u8, 26 | } 27 | 28 | pub fn to_u16(hi: u8, lo: u8) -> u16 { 29 | return (hi as u16) << 8 | (lo as u16); 30 | } 31 | 32 | #[derive(Debug, PartialEq)] 33 | pub enum AddressingMode { 34 | ABS, // Absolute 35 | ABX, // 36 | ABY, // 37 | ACC, // Accumulator 38 | IMM, // Immediate 39 | IMP, // 40 | IND, // Indirect 41 | IZX, // 42 | IZY, // 43 | REL, // Relative 44 | ZP0, // Zero Page 0 45 | ZPX, // Zero Page X 46 | ZPY, // Zero Page Y 47 | } 48 | 49 | #[derive(Debug, PartialEq)] 50 | pub enum Opcode { 51 | ADC, 52 | AND, 53 | ASL, 54 | BCC, 55 | BCS, 56 | BEQ, 57 | BIT, 58 | BMI, 59 | BNE, 60 | BPL, 61 | BRK, 62 | BVC, 63 | BVS, 64 | CLC, 65 | CLD, 66 | CLI, 67 | CLV, 68 | CMP, 69 | CPX, 70 | CPY, 71 | DEC, 72 | DEX, 73 | DEY, 74 | EOR, 75 | ERR, // Unknown opcode -> Error 76 | INC, 77 | INX, 78 | INY, 79 | JMP, 80 | JSR, 81 | LDA, 82 | LDX, 83 | LDY, 84 | LSR, 85 | NOP, 86 | ORA, 87 | PHA, 88 | PHP, 89 | PLA, 90 | PLP, 91 | ROL, 92 | ROR, 93 | RTI, 94 | RTS, 95 | SBC, 96 | SEC, 97 | SED, 98 | SEI, 99 | STA, 100 | STX, 101 | STY, 102 | TAX, 103 | TAY, 104 | TSX, 105 | TXA, 106 | TXS, 107 | TYA, 108 | } 109 | 110 | pub struct Instruction { 111 | pub opcode: Opcode, 112 | pub mode: AddressingMode, 113 | pub cycles: u8, 114 | } 115 | 116 | #[rustfmt::skip] 117 | pub const INSTRUCTION_LOOKUP: [Instruction; 256] = [ 118 | // 0x00 119 | Instruction { opcode: Opcode::BRK, mode: AddressingMode::IMP, cycles: 7 }, 120 | Instruction { opcode: Opcode::ORA, mode: AddressingMode::IZX, cycles: 6 }, 121 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 122 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 123 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 3 }, 124 | Instruction { opcode: Opcode::ORA, mode: AddressingMode::ZP0, cycles: 3 }, 125 | Instruction { opcode: Opcode::ASL, mode: AddressingMode::ZP0, cycles: 5 }, 126 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 127 | Instruction { opcode: Opcode::PHP, mode: AddressingMode::IMP, cycles: 3 }, 128 | Instruction { opcode: Opcode::ORA, mode: AddressingMode::IMM, cycles: 2 }, 129 | Instruction { opcode: Opcode::ASL, mode: AddressingMode::ACC, cycles: 2 }, 130 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 131 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 132 | Instruction { opcode: Opcode::ORA, mode: AddressingMode::ABS, cycles: 4 }, 133 | Instruction { opcode: Opcode::ASL, mode: AddressingMode::ABS, cycles: 6 }, 134 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 135 | // 0x10 136 | Instruction { opcode: Opcode::BPL, mode: AddressingMode::REL, cycles: 2 }, 137 | Instruction { opcode: Opcode::ORA, mode: AddressingMode::IZY, cycles: 5 }, 138 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 139 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 140 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 141 | Instruction { opcode: Opcode::ORA, mode: AddressingMode::ZPX, cycles: 4 }, 142 | Instruction { opcode: Opcode::ASL, mode: AddressingMode::ZPX, cycles: 6 }, 143 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 144 | Instruction { opcode: Opcode::CLC, mode: AddressingMode::IMP, cycles: 2 }, 145 | Instruction { opcode: Opcode::ORA, mode: AddressingMode::ABY, cycles: 4 }, 146 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 147 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 148 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 149 | Instruction { opcode: Opcode::ORA, mode: AddressingMode::ABX, cycles: 4 }, 150 | Instruction { opcode: Opcode::ASL, mode: AddressingMode::ABX, cycles: 7 }, 151 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 152 | // 0x20 153 | Instruction { opcode: Opcode::JSR, mode: AddressingMode::ABS, cycles: 6 }, 154 | Instruction { opcode: Opcode::AND, mode: AddressingMode::IZX, cycles: 6 }, 155 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 156 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 157 | Instruction { opcode: Opcode::BIT, mode: AddressingMode::ZP0, cycles: 3 }, 158 | Instruction { opcode: Opcode::AND, mode: AddressingMode::ZP0, cycles: 3 }, 159 | Instruction { opcode: Opcode::ROL, mode: AddressingMode::ZP0, cycles: 5 }, 160 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 161 | Instruction { opcode: Opcode::PLP, mode: AddressingMode::IMP, cycles: 4 }, 162 | Instruction { opcode: Opcode::AND, mode: AddressingMode::IMM, cycles: 2 }, 163 | Instruction { opcode: Opcode::ROL, mode: AddressingMode::ACC, cycles: 2 }, 164 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 165 | Instruction { opcode: Opcode::BIT, mode: AddressingMode::ABS, cycles: 4 }, 166 | Instruction { opcode: Opcode::AND, mode: AddressingMode::ABS, cycles: 4 }, 167 | Instruction { opcode: Opcode::ROL, mode: AddressingMode::ABS, cycles: 6 }, 168 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 169 | // 0x30 170 | Instruction { opcode: Opcode::BMI, mode: AddressingMode::REL, cycles: 2 }, 171 | Instruction { opcode: Opcode::AND, mode: AddressingMode::IZY, cycles: 5 }, 172 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 173 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 174 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 175 | Instruction { opcode: Opcode::AND, mode: AddressingMode::ZPX, cycles: 4 }, 176 | Instruction { opcode: Opcode::ROL, mode: AddressingMode::ZPX, cycles: 6 }, 177 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 178 | Instruction { opcode: Opcode::SEC, mode: AddressingMode::IMP, cycles: 2 }, 179 | Instruction { opcode: Opcode::AND, mode: AddressingMode::ABY, cycles: 4 }, 180 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 181 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 182 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 183 | Instruction { opcode: Opcode::AND, mode: AddressingMode::ABX, cycles: 4 }, 184 | Instruction { opcode: Opcode::ROL, mode: AddressingMode::ABX, cycles: 7 }, 185 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 186 | // 0x40 187 | Instruction { opcode: Opcode::RTI, mode: AddressingMode::IMP, cycles: 6 }, 188 | Instruction { opcode: Opcode::EOR, mode: AddressingMode::IZX, cycles: 6 }, 189 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 190 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 191 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 3 }, 192 | Instruction { opcode: Opcode::EOR, mode: AddressingMode::ZP0, cycles: 3 }, 193 | Instruction { opcode: Opcode::LSR, mode: AddressingMode::ZP0, cycles: 5 }, 194 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 195 | Instruction { opcode: Opcode::PHA, mode: AddressingMode::IMP, cycles: 3 }, 196 | Instruction { opcode: Opcode::EOR, mode: AddressingMode::IMM, cycles: 2 }, 197 | Instruction { opcode: Opcode::LSR, mode: AddressingMode::ACC, cycles: 2 }, 198 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 199 | Instruction { opcode: Opcode::JMP, mode: AddressingMode::ABS, cycles: 3 }, 200 | Instruction { opcode: Opcode::EOR, mode: AddressingMode::ABS, cycles: 4 }, 201 | Instruction { opcode: Opcode::LSR, mode: AddressingMode::ABS, cycles: 6 }, 202 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 203 | // 0x50 204 | Instruction { opcode: Opcode::BVC, mode: AddressingMode::REL, cycles: 2 }, 205 | Instruction { opcode: Opcode::EOR, mode: AddressingMode::IZY, cycles: 5 }, 206 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 207 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 208 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 209 | Instruction { opcode: Opcode::EOR, mode: AddressingMode::ZPX, cycles: 4 }, 210 | Instruction { opcode: Opcode::LSR, mode: AddressingMode::ZPX, cycles: 6 }, 211 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 212 | Instruction { opcode: Opcode::CLI, mode: AddressingMode::IMP, cycles: 2 }, 213 | Instruction { opcode: Opcode::EOR, mode: AddressingMode::ABY, cycles: 4 }, 214 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 215 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 216 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 217 | Instruction { opcode: Opcode::EOR, mode: AddressingMode::ABX, cycles: 4 }, 218 | Instruction { opcode: Opcode::LSR, mode: AddressingMode::ABX, cycles: 7 }, 219 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 220 | // 0x60 221 | Instruction { opcode: Opcode::RTS, mode: AddressingMode::IMP, cycles: 6 }, 222 | Instruction { opcode: Opcode::ADC, mode: AddressingMode::IZX, cycles: 6 }, 223 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 224 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 225 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 3 }, 226 | Instruction { opcode: Opcode::ADC, mode: AddressingMode::ZP0, cycles: 3 }, 227 | Instruction { opcode: Opcode::ROR, mode: AddressingMode::ZP0, cycles: 5 }, 228 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 229 | Instruction { opcode: Opcode::PLA, mode: AddressingMode::IMP, cycles: 4 }, 230 | Instruction { opcode: Opcode::ADC, mode: AddressingMode::IMM, cycles: 2 }, 231 | Instruction { opcode: Opcode::ROR, mode: AddressingMode::ACC, cycles: 2 }, 232 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 233 | Instruction { opcode: Opcode::JMP, mode: AddressingMode::IND, cycles: 5 }, 234 | Instruction { opcode: Opcode::ADC, mode: AddressingMode::ABS, cycles: 4 }, 235 | Instruction { opcode: Opcode::ROR, mode: AddressingMode::ABS, cycles: 6 }, 236 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 237 | // 0x70 238 | Instruction { opcode: Opcode::BVS, mode: AddressingMode::REL, cycles: 2 }, 239 | Instruction { opcode: Opcode::ADC, mode: AddressingMode::IZY, cycles: 5 }, 240 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 241 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 242 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 243 | Instruction { opcode: Opcode::ADC, mode: AddressingMode::ZPX, cycles: 4 }, 244 | Instruction { opcode: Opcode::ROR, mode: AddressingMode::ZPX, cycles: 6 }, 245 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 246 | Instruction { opcode: Opcode::SEI, mode: AddressingMode::IMP, cycles: 2 }, 247 | Instruction { opcode: Opcode::ADC, mode: AddressingMode::ABY, cycles: 4 }, 248 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 249 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 250 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 251 | Instruction { opcode: Opcode::ADC, mode: AddressingMode::ABX, cycles: 4 }, 252 | Instruction { opcode: Opcode::ROR, mode: AddressingMode::ABX, cycles: 7 }, 253 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 254 | // 0x80 255 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 256 | Instruction { opcode: Opcode::STA, mode: AddressingMode::IZX, cycles: 6 }, 257 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 258 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 259 | Instruction { opcode: Opcode::STY, mode: AddressingMode::ZP0, cycles: 3 }, 260 | Instruction { opcode: Opcode::STA, mode: AddressingMode::ZP0, cycles: 3 }, 261 | Instruction { opcode: Opcode::STX, mode: AddressingMode::ZP0, cycles: 3 }, 262 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 3 }, 263 | Instruction { opcode: Opcode::DEY, mode: AddressingMode::IMP, cycles: 2 }, 264 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 265 | Instruction { opcode: Opcode::TXA, mode: AddressingMode::IMP, cycles: 2 }, 266 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 267 | Instruction { opcode: Opcode::STY, mode: AddressingMode::ABS, cycles: 4 }, 268 | Instruction { opcode: Opcode::STA, mode: AddressingMode::ABS, cycles: 4 }, 269 | Instruction { opcode: Opcode::STX, mode: AddressingMode::ABS, cycles: 4 }, 270 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 271 | // 0x90 272 | Instruction { opcode: Opcode::BCC, mode: AddressingMode::REL, cycles: 2 }, 273 | Instruction { opcode: Opcode::STA, mode: AddressingMode::IZY, cycles: 6 }, 274 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 275 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 276 | Instruction { opcode: Opcode::STY, mode: AddressingMode::ZPX, cycles: 4 }, 277 | Instruction { opcode: Opcode::STA, mode: AddressingMode::ZPX, cycles: 4 }, 278 | Instruction { opcode: Opcode::STX, mode: AddressingMode::ZPY, cycles: 4 }, 279 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 280 | Instruction { opcode: Opcode::TYA, mode: AddressingMode::IMP, cycles: 2 }, 281 | Instruction { opcode: Opcode::STA, mode: AddressingMode::ABY, cycles: 5 }, 282 | Instruction { opcode: Opcode::TXS, mode: AddressingMode::IMP, cycles: 2 }, 283 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 284 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 285 | Instruction { opcode: Opcode::STA, mode: AddressingMode::ABX, cycles: 5 }, 286 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 287 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 288 | // 0xA0 289 | Instruction { opcode: Opcode::LDY, mode: AddressingMode::IMM, cycles: 2 }, 290 | Instruction { opcode: Opcode::LDA, mode: AddressingMode::IZX, cycles: 6 }, 291 | Instruction { opcode: Opcode::LDX, mode: AddressingMode::IMM, cycles: 2 }, 292 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 293 | Instruction { opcode: Opcode::LDY, mode: AddressingMode::ZP0, cycles: 3 }, 294 | Instruction { opcode: Opcode::LDA, mode: AddressingMode::ZP0, cycles: 3 }, 295 | Instruction { opcode: Opcode::LDX, mode: AddressingMode::ZP0, cycles: 3 }, 296 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 3 }, 297 | Instruction { opcode: Opcode::TAY, mode: AddressingMode::IMP, cycles: 2 }, 298 | Instruction { opcode: Opcode::LDA, mode: AddressingMode::IMM, cycles: 2 }, 299 | Instruction { opcode: Opcode::TAX, mode: AddressingMode::IMP, cycles: 2 }, 300 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 301 | Instruction { opcode: Opcode::LDY, mode: AddressingMode::ABS, cycles: 4 }, 302 | Instruction { opcode: Opcode::LDA, mode: AddressingMode::ABS, cycles: 4 }, 303 | Instruction { opcode: Opcode::LDX, mode: AddressingMode::ABS, cycles: 4 }, 304 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 305 | // 0xB0 306 | Instruction { opcode: Opcode::BCS, mode: AddressingMode::REL, cycles: 2 }, 307 | Instruction { opcode: Opcode::LDA, mode: AddressingMode::IZY, cycles: 5 }, 308 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 309 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 310 | Instruction { opcode: Opcode::LDY, mode: AddressingMode::ZPX, cycles: 4 }, 311 | Instruction { opcode: Opcode::LDA, mode: AddressingMode::ZPX, cycles: 4 }, 312 | Instruction { opcode: Opcode::LDX, mode: AddressingMode::ZPY, cycles: 4 }, 313 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 314 | Instruction { opcode: Opcode::CLV, mode: AddressingMode::IMP, cycles: 2 }, 315 | Instruction { opcode: Opcode::LDA, mode: AddressingMode::ABY, cycles: 4 }, 316 | Instruction { opcode: Opcode::TSX, mode: AddressingMode::IMP, cycles: 2 }, 317 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 318 | Instruction { opcode: Opcode::LDY, mode: AddressingMode::ABX, cycles: 4 }, 319 | Instruction { opcode: Opcode::LDA, mode: AddressingMode::ABX, cycles: 4 }, 320 | Instruction { opcode: Opcode::LDX, mode: AddressingMode::ABY, cycles: 4 }, 321 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 322 | // 0xC0 323 | Instruction { opcode: Opcode::CPY, mode: AddressingMode::IMM, cycles: 2 }, 324 | Instruction { opcode: Opcode::CMP, mode: AddressingMode::IZX, cycles: 6 }, 325 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 326 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 327 | Instruction { opcode: Opcode::CPY, mode: AddressingMode::ZP0, cycles: 3 }, 328 | Instruction { opcode: Opcode::CMP, mode: AddressingMode::ZP0, cycles: 3 }, 329 | Instruction { opcode: Opcode::DEC, mode: AddressingMode::ZP0, cycles: 5 }, 330 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 331 | Instruction { opcode: Opcode::INY, mode: AddressingMode::IMP, cycles: 2 }, 332 | Instruction { opcode: Opcode::CMP, mode: AddressingMode::IMM, cycles: 2 }, 333 | Instruction { opcode: Opcode::DEX, mode: AddressingMode::IMP, cycles: 2 }, 334 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 335 | Instruction { opcode: Opcode::CPY, mode: AddressingMode::ABS, cycles: 4 }, 336 | Instruction { opcode: Opcode::CMP, mode: AddressingMode::ABS, cycles: 4 }, 337 | Instruction { opcode: Opcode::DEC, mode: AddressingMode::ABS, cycles: 6 }, 338 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 339 | // 0xD0 340 | Instruction { opcode: Opcode::BNE, mode: AddressingMode::REL, cycles: 2 }, 341 | Instruction { opcode: Opcode::CMP, mode: AddressingMode::IZY, cycles: 5 }, 342 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 343 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 344 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 345 | Instruction { opcode: Opcode::CMP, mode: AddressingMode::ZPX, cycles: 4 }, 346 | Instruction { opcode: Opcode::DEC, mode: AddressingMode::ZPX, cycles: 6 }, 347 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 348 | Instruction { opcode: Opcode::CLD, mode: AddressingMode::IMP, cycles: 2 }, 349 | Instruction { opcode: Opcode::CMP, mode: AddressingMode::ABY, cycles: 4 }, 350 | Instruction { opcode: Opcode::NOP, mode: AddressingMode::IMP, cycles: 2 }, 351 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 352 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 353 | Instruction { opcode: Opcode::CMP, mode: AddressingMode::ABX, cycles: 4 }, 354 | Instruction { opcode: Opcode::DEC, mode: AddressingMode::ABX, cycles: 7 }, 355 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 356 | // 0xE0 357 | Instruction { opcode: Opcode::CPX, mode: AddressingMode::IMM, cycles: 2 }, 358 | Instruction { opcode: Opcode::SBC, mode: AddressingMode::IZX, cycles: 6 }, 359 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 360 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 361 | Instruction { opcode: Opcode::CPX, mode: AddressingMode::ZP0, cycles: 3 }, 362 | Instruction { opcode: Opcode::SBC, mode: AddressingMode::ZP0, cycles: 3 }, 363 | Instruction { opcode: Opcode::INC, mode: AddressingMode::ZP0, cycles: 5 }, 364 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 5 }, 365 | Instruction { opcode: Opcode::INX, mode: AddressingMode::IMP, cycles: 2 }, 366 | Instruction { opcode: Opcode::SBC, mode: AddressingMode::IMM, cycles: 2 }, 367 | Instruction { opcode: Opcode::NOP, mode: AddressingMode::IMP, cycles: 2 }, 368 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 369 | Instruction { opcode: Opcode::CPX, mode: AddressingMode::ABS, cycles: 4 }, 370 | Instruction { opcode: Opcode::SBC, mode: AddressingMode::ABS, cycles: 4 }, 371 | Instruction { opcode: Opcode::INC, mode: AddressingMode::ABS, cycles: 6 }, 372 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 373 | // 0xF0 374 | Instruction { opcode: Opcode::BEQ, mode: AddressingMode::REL, cycles: 2 }, 375 | Instruction { opcode: Opcode::SBC, mode: AddressingMode::IZY, cycles: 5 }, 376 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 2 }, 377 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 8 }, 378 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 379 | Instruction { opcode: Opcode::SBC, mode: AddressingMode::ZPX, cycles: 4 }, 380 | Instruction { opcode: Opcode::INC, mode: AddressingMode::ZPX, cycles: 6 }, 381 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 6 }, 382 | Instruction { opcode: Opcode::SED, mode: AddressingMode::IMP, cycles: 2 }, 383 | Instruction { opcode: Opcode::SBC, mode: AddressingMode::ABY, cycles: 4 }, 384 | Instruction { opcode: Opcode::NOP, mode: AddressingMode::IMP, cycles: 2 }, 385 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 386 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 4 }, 387 | Instruction { opcode: Opcode::SBC, mode: AddressingMode::ABX, cycles: 4 }, 388 | Instruction { opcode: Opcode::INC, mode: AddressingMode::ABX, cycles: 7 }, 389 | Instruction { opcode: Opcode::ERR, mode: AddressingMode::IMP, cycles: 7 }, 390 | ]; 391 | 392 | impl Flags { 393 | pub fn set_zn(&mut self, v: u8) { 394 | self.Z = v == 0; 395 | self.N = v & 0x80 != 0; 396 | } 397 | 398 | pub fn to_byte(&self) -> u8 { 399 | (self.C as u8) << 0 400 | | (self.Z as u8) << 1 401 | | (self.I as u8) << 2 402 | | (self.D as u8) << 3 403 | | (self.B as u8) << 4 404 | | (self.U as u8) << 5 405 | | (self.O as u8) << 6 406 | | (self.N as u8) << 7 407 | } 408 | 409 | pub fn set_byte(&mut self, b: u8) { 410 | self.C = ((b >> 0) & 1) != 0; 411 | self.Z = ((b >> 1) & 1) != 0; 412 | self.I = ((b >> 2) & 1) != 0; 413 | self.D = ((b >> 3) & 1) != 0; 414 | self.O = ((b >> 6) & 1) != 0; 415 | self.N = ((b >> 7) & 1) != 0; 416 | } 417 | } 418 | 419 | impl Cpu { 420 | pub fn new() -> Self { 421 | Cpu { 422 | bus: Bus::new(), 423 | PC: 0, 424 | SP: 0, 425 | A: 0, 426 | X: 0, 427 | Y: 0, 428 | flags: Flags::default(), 429 | total_cycles: 0, 430 | cycles: 0, 431 | } 432 | } 433 | 434 | pub fn reset(&mut self) { 435 | self.PC = self.read_from_location_u16(0xFFFC); 436 | self.SP = 0xFD; 437 | self.total_cycles = 0; 438 | self.cycles = 0; 439 | self.A = 0; 440 | self.X = 0; 441 | self.Y = 0; 442 | self.flags = Flags::default(); 443 | self.flags.I = true; 444 | self.flags.U = true; 445 | self.cycles = 7; 446 | } 447 | 448 | pub fn nmi(&mut self) { 449 | self.push_u16(self.PC); 450 | 451 | self.flags.B = false; 452 | self.flags.U = true; 453 | self.flags.I = true; 454 | 455 | self.push_flags(); 456 | 457 | self.PC = self.read_from_location_u16(0xFFFA); 458 | 459 | self.cycles = 8; 460 | } 461 | 462 | pub fn clock(&mut self) { 463 | self.total_cycles += 1; 464 | 465 | if self.cycles > 0 { 466 | self.cycles -= 1; 467 | return; 468 | } 469 | 470 | let ins_code = self.bus.cpu_read(self.PC); 471 | self.PC += 1; 472 | 473 | let ins = &INSTRUCTION_LOOKUP[ins_code as usize]; 474 | 475 | self.cycles = ins.cycles; 476 | let addr = self.read_addr(&ins); 477 | 478 | match ins.opcode { 479 | Opcode::JMP => { 480 | self.PC = addr; 481 | } 482 | Opcode::LDX => { 483 | let v = self.bus.cpu_read(addr); 484 | self.X = v as u8; 485 | self.flags.set_zn(self.X); 486 | } 487 | Opcode::LDA => { 488 | let v = self.bus.cpu_read(addr); 489 | self.A = v as u8; 490 | self.flags.set_zn(self.A); 491 | } 492 | Opcode::LDY => { 493 | let v = self.bus.cpu_read(addr); 494 | self.Y = v as u8; 495 | self.flags.set_zn(self.Y); 496 | } 497 | Opcode::JSR => { 498 | self.PC -= 1; 499 | self.push_u16(self.PC); 500 | self.PC = addr; 501 | } 502 | Opcode::RTS => { 503 | self.PC = self.pop_u16(); 504 | self.PC += 1; 505 | } 506 | Opcode::NOP => {} 507 | Opcode::SEC => { 508 | self.flags.C = true; 509 | } 510 | Opcode::CLC => { 511 | self.flags.C = false; 512 | } 513 | Opcode::SED => { 514 | self.flags.D = true; 515 | } 516 | Opcode::CLD => { 517 | self.flags.D = false; 518 | } 519 | Opcode::SEI => { 520 | self.flags.I = true; 521 | } 522 | Opcode::CLI => { 523 | self.flags.I = false; 524 | } 525 | Opcode::CLV => { 526 | self.flags.O = false; 527 | } 528 | Opcode::BCS => { 529 | if self.flags.C { 530 | self.branch_jump(addr); 531 | } 532 | } 533 | Opcode::BCC => { 534 | if !self.flags.C { 535 | self.branch_jump(addr); 536 | } 537 | } 538 | Opcode::BEQ => { 539 | if self.flags.Z { 540 | self.branch_jump(addr); 541 | } 542 | } 543 | Opcode::BNE => { 544 | if !self.flags.Z { 545 | self.branch_jump(addr); 546 | } 547 | } 548 | Opcode::BMI => { 549 | if self.flags.N { 550 | self.branch_jump(addr); 551 | } 552 | } 553 | Opcode::BPL => { 554 | if !self.flags.N { 555 | self.branch_jump(addr); 556 | } 557 | } 558 | Opcode::BVC => { 559 | if !self.flags.O { 560 | self.branch_jump(addr); 561 | } 562 | } 563 | Opcode::BVS => { 564 | if self.flags.O { 565 | self.branch_jump(addr); 566 | } 567 | } 568 | Opcode::STA => { 569 | self.bus.cpu_write(addr, self.A); 570 | } 571 | Opcode::STX => { 572 | self.bus.cpu_write(addr, self.X); 573 | } 574 | Opcode::STY => { 575 | self.bus.cpu_write(addr, self.Y); 576 | } 577 | Opcode::BIT => { 578 | let v = self.bus.cpu_read(addr); 579 | let r = self.A & (v as u8); 580 | self.flags.Z = r == 0; 581 | self.flags.O = (v & (1 << 6)) != 0; 582 | self.flags.N = (v & (1 << 7)) != 0; 583 | } 584 | Opcode::PHP => { 585 | self.push_flags(); 586 | } 587 | Opcode::PLP => { 588 | let flag = self.pop(); 589 | self.flags.set_byte(flag); 590 | } 591 | Opcode::PHA => { 592 | self.push(self.A); 593 | } 594 | Opcode::PLA => { 595 | self.A = self.pop(); 596 | self.flags.set_zn(self.A); 597 | } 598 | Opcode::CMP => { 599 | let v = self.bus.cpu_read(addr); 600 | self.flags.C = self.A >= v; 601 | self.flags.set_zn(self.A.wrapping_sub(v)); 602 | } 603 | Opcode::CPX => { 604 | let v = self.bus.cpu_read(addr); 605 | self.flags.C = self.X >= v; 606 | self.flags.set_zn(self.X.wrapping_sub(v)); 607 | } 608 | Opcode::CPY => { 609 | let v = self.bus.cpu_read(addr); 610 | self.flags.C = self.Y >= v; 611 | self.flags.set_zn(self.Y.wrapping_sub(v)); 612 | } 613 | Opcode::AND => { 614 | let v = self.bus.cpu_read(addr); 615 | self.A = self.A & v; 616 | self.flags.set_zn(self.A); 617 | } 618 | Opcode::ORA => { 619 | let v = self.bus.cpu_read(addr); 620 | self.A = self.A | v; 621 | self.flags.set_zn(self.A); 622 | } 623 | Opcode::EOR => { 624 | let v = self.bus.cpu_read(addr); 625 | self.A = self.A ^ v; 626 | self.flags.set_zn(self.A); 627 | } 628 | Opcode::ADC => { 629 | let v = self.bus.cpu_read(addr) as u16; 630 | let res: u16 = (self.A as u16) + v + (self.flags.C as u16); 631 | self.flags.C = res > 0xFF; 632 | self.flags.O = !((self.A as u16) ^ v) & ((self.A as u16) ^ res) & 0x80 != 0; 633 | self.A = res as u8; 634 | self.flags.set_zn(self.A); 635 | } 636 | Opcode::SBC => { 637 | let mut v = self.bus.cpu_read(addr) as u16; 638 | v = v ^ 0x00FF; 639 | let res: u16 = (self.A as u16) + v + (self.flags.C as u16); 640 | self.flags.C = res > 0xFF; 641 | self.flags.O = !((self.A as u16) ^ v) & ((self.A as u16) ^ res) & 0x80 != 0; 642 | self.A = res as u8; 643 | self.flags.set_zn(self.A); 644 | } 645 | Opcode::INY => { 646 | self.Y = self.Y.wrapping_add(1); 647 | self.flags.set_zn(self.Y); 648 | } 649 | Opcode::INX => { 650 | self.X = self.X.wrapping_add(1); 651 | self.flags.set_zn(self.X); 652 | } 653 | Opcode::DEX => { 654 | self.X = self.X.wrapping_sub(1); 655 | self.flags.set_zn(self.X); 656 | } 657 | Opcode::DEY => { 658 | self.Y = self.Y.wrapping_sub(1); 659 | self.flags.set_zn(self.Y); 660 | } 661 | Opcode::TAY => { 662 | self.Y = self.A; 663 | self.flags.set_zn(self.Y); 664 | } 665 | Opcode::TAX => { 666 | self.X = self.A; 667 | self.flags.set_zn(self.X); 668 | } 669 | Opcode::TYA => { 670 | self.A = self.Y; 671 | self.flags.set_zn(self.A); 672 | } 673 | Opcode::TXA => { 674 | self.A = self.X; 675 | self.flags.set_zn(self.A); 676 | } 677 | Opcode::TSX => { 678 | self.X = self.SP; 679 | self.flags.set_zn(self.X); 680 | } 681 | Opcode::TXS => { 682 | self.SP = self.X; 683 | } 684 | Opcode::INC => { 685 | let mut v = self.bus.cpu_read(addr); 686 | v = v.wrapping_add(1); 687 | self.flags.set_zn(v); 688 | self.bus.cpu_write(addr, v); 689 | } 690 | Opcode::DEC => { 691 | let mut v = self.bus.cpu_read(addr); 692 | v = v.wrapping_sub(1); 693 | self.flags.set_zn(v); 694 | self.bus.cpu_write(addr, v); 695 | } 696 | Opcode::RTI => { 697 | let flag = self.pop(); 698 | self.flags.set_byte(flag); 699 | self.PC = self.pop_u16(); 700 | } 701 | Opcode::LSR | Opcode::ROR => { 702 | let data = if ins.mode == AddressingMode::ACC { 703 | addr 704 | } else { 705 | self.bus.cpu_read(addr) as u16 706 | }; 707 | 708 | let prev_c = self.flags.C; 709 | self.flags.C = (data & 0x0001) != 0; 710 | let mut res = (data >> 1) as u8; 711 | if ins.opcode == Opcode::ROR && prev_c { 712 | res = res | 1 << 7; 713 | } 714 | self.flags.set_zn(res); 715 | 716 | if ins.mode == AddressingMode::ACC { 717 | self.A = res; 718 | } else { 719 | self.bus.cpu_write(addr, res); 720 | } 721 | } 722 | Opcode::ASL | Opcode::ROL => { 723 | let data = if ins.mode == AddressingMode::ACC { 724 | addr 725 | } else { 726 | self.bus.cpu_read(addr) as u16 727 | }; 728 | 729 | let prev_c = self.flags.C; 730 | self.flags.C = (data & 0x80) != 0; 731 | let mut res = (data << 1) as u8; 732 | if ins.opcode == Opcode::ROL && prev_c { 733 | res = res | 1; 734 | } 735 | self.flags.set_zn(res); 736 | 737 | if ins.mode == AddressingMode::ACC { 738 | self.A = res; 739 | } else { 740 | self.bus.cpu_write(addr, res); 741 | } 742 | } 743 | Opcode::BRK => { 744 | self.PC += 1; 745 | self.flags.I = true; 746 | self.push_u16(self.PC); 747 | 748 | self.push_flags(); 749 | 750 | self.PC = self.read_from_location_u16(0xFFFE); 751 | } 752 | Opcode::ERR => {} 753 | } 754 | 755 | self.cycles -= 1; 756 | } 757 | 758 | fn branch_jump(&mut self, addr: u16) { 759 | self.cycles += 1; 760 | 761 | // Different page? +1 cycle 762 | if (addr & 0xFF00) != (self.PC & 0xFF00) { 763 | self.cycles += 1; 764 | } 765 | 766 | self.PC = addr; 767 | } 768 | 769 | fn push_flags(&mut self) { 770 | let mut st = self.flags.to_byte(); 771 | st = st | (1 << 5); 772 | st = st | (1 << 4); 773 | self.push(st); 774 | } 775 | 776 | fn push(&mut self, v: u8) { 777 | self.bus.cpu_write(0x0100 + self.SP as u16, v); 778 | self.SP -= 1; 779 | } 780 | 781 | fn push_u16(&mut self, v: u16) { 782 | self.push((v >> 8) as u8); 783 | self.push((v & 0x00FF) as u8); 784 | } 785 | 786 | fn pop(&mut self) -> u8 { 787 | self.SP += 1; 788 | return self.bus.cpu_read(0x0100 + self.SP as u16); 789 | } 790 | 791 | fn pop_u16(&mut self) -> u16 { 792 | let lo = self.pop(); 793 | let hi = self.pop(); 794 | to_u16(hi, lo) 795 | } 796 | 797 | fn read_addr(&mut self, ins: &Instruction) -> u16 { 798 | match ins.mode { 799 | AddressingMode::ABS => return self.read_addr_abs(), 800 | AddressingMode::ABY => return self.read_addr_aby(ins.opcode != Opcode::STA), 801 | AddressingMode::ABX => return self.read_addr_abx(ins.opcode != Opcode::STA), 802 | AddressingMode::IND => return self.read_addr_ind(), 803 | AddressingMode::IZX => return self.read_addr_izx(), 804 | AddressingMode::IZY => return self.read_addr_izy(), 805 | AddressingMode::IMM => return self.read_addr_imm(), 806 | AddressingMode::IMP => return self.read_addr_imp(), 807 | AddressingMode::ACC => return self.read_addr_acc(), 808 | AddressingMode::ZP0 => return self.read_addr_zp0(), 809 | AddressingMode::ZPX => return self.read_addr_zpx(), 810 | AddressingMode::ZPY => return self.read_addr_zpy(), 811 | AddressingMode::REL => return self.read_addr_rel(), 812 | } 813 | } 814 | 815 | fn read_from_location(&mut self, v: u8) -> u16 { 816 | return to_u16( 817 | self.bus.cpu_read(v.wrapping_add(1) as u16), 818 | self.bus.cpu_read(v as u16), 819 | ); 820 | } 821 | 822 | fn read_from_location_u16(&mut self, v: u16) -> u16 { 823 | return to_u16( 824 | self.bus.cpu_read(v.wrapping_add(1) as u16), 825 | self.bus.cpu_read(v as u16), 826 | ); 827 | } 828 | 829 | fn read_addr_abs(&mut self) -> u16 { 830 | let lo = self.bus.cpu_read(self.PC); 831 | self.PC += 1; 832 | let hi = self.bus.cpu_read(self.PC); 833 | self.PC += 1; 834 | return to_u16(hi, lo); 835 | } 836 | 837 | fn read_addr_abx(&mut self, cross_page_check: bool) -> u16 { 838 | let addr = self.read_addr_abs(); 839 | let off = addr.wrapping_add(self.X as u16); 840 | if cross_page_check && off & 0xFF00 != addr & 0xFF00 { 841 | self.cycles += 1; 842 | } 843 | return off; 844 | } 845 | 846 | fn read_addr_aby(&mut self, cross_page_check: bool) -> u16 { 847 | let addr = self.read_addr_abs(); 848 | let off = addr.wrapping_add(self.Y as u16); 849 | if cross_page_check && off & 0xFF00 != addr & 0xFF00 { 850 | self.cycles += 1; 851 | } 852 | return off; 853 | } 854 | 855 | fn read_addr_ind(&mut self) -> u16 { 856 | let addr = self.read_addr_abs(); 857 | 858 | // NES had a bug on page boundary -> simulate it: 859 | if addr & 0x00FF == 0x00FF { 860 | return to_u16(self.bus.cpu_read(addr & 0xFF00), self.bus.cpu_read(addr)); 861 | } 862 | 863 | return to_u16(self.bus.cpu_read(addr + 1), self.bus.cpu_read(addr)); 864 | } 865 | 866 | fn read_addr_izx(&mut self) -> u16 { 867 | let mut addr = self.bus.cpu_read(self.PC); 868 | self.PC += 1; 869 | 870 | addr = addr.wrapping_add(self.X); 871 | 872 | return self.read_from_location(addr); 873 | } 874 | 875 | fn read_addr_izy(&mut self) -> u16 { 876 | let addr = self.bus.cpu_read(self.PC); 877 | self.PC += 1; 878 | 879 | let x = self.read_from_location(addr); 880 | let off = x.wrapping_add(self.Y as u16); 881 | if off & 0xFF00 != x & 0xFF00 { 882 | self.cycles += 1; 883 | } 884 | return off; 885 | } 886 | 887 | fn read_addr_imm(&mut self) -> u16 { 888 | let x = self.PC; 889 | self.PC += 1; 890 | return x; 891 | } 892 | 893 | fn read_addr_imp(&mut self) -> u16 { 894 | return 0; 895 | } 896 | 897 | fn read_addr_acc(&mut self) -> u16 { 898 | return self.A as u16; 899 | } 900 | 901 | fn read_addr_zp0(&mut self) -> u16 { 902 | let x = self.bus.cpu_read(self.PC); 903 | self.PC += 1; 904 | return x as u16; 905 | } 906 | 907 | fn read_addr_zpx(&mut self) -> u16 { 908 | let mut x = (self.bus.cpu_read(self.PC) as u16) + (self.X as u16); 909 | x = x & 0x00FF; 910 | self.PC += 1; 911 | return x; 912 | } 913 | 914 | fn read_addr_zpy(&mut self) -> u16 { 915 | let mut x = (self.bus.cpu_read(self.PC) as u16) + (self.Y as u16); 916 | x = x & 0x00FF; 917 | self.PC += 1; 918 | return x; 919 | } 920 | 921 | fn read_addr_rel(&mut self) -> u16 { 922 | let x = self.bus.cpu_read(self.PC); 923 | 924 | self.PC += 1; 925 | return ((self.PC as i16) + (x as i8) as i16) as u16; 926 | } 927 | } 928 | -------------------------------------------------------------------------------- /src/nes/disasm.rs: -------------------------------------------------------------------------------- 1 | use super::bus::Bus; 2 | use super::cpu::{AddressingMode, Instruction, Opcode, INSTRUCTION_LOOKUP}; 3 | use std::fmt; 4 | use std::io::Write; 5 | 6 | fn get_instruction_size(ins: &Instruction) -> u16 { 7 | match ins.mode { 8 | AddressingMode::ABS => 3, 9 | AddressingMode::ABX => 3, 10 | AddressingMode::ABY => 3, 11 | AddressingMode::IMM => 2, 12 | AddressingMode::REL => 2, 13 | AddressingMode::ACC => 1, 14 | AddressingMode::IMP => 1, 15 | AddressingMode::IND => 3, 16 | AddressingMode::ZP0 => 2, 17 | AddressingMode::ZPX => 2, 18 | AddressingMode::ZPY => 2, 19 | AddressingMode::IZX => 2, 20 | AddressingMode::IZY => 2, 21 | } 22 | } 23 | 24 | impl fmt::Display for Opcode { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | let name = match *self { 27 | Opcode::ADC => "ADC", 28 | Opcode::AND => "AND", 29 | Opcode::ASL => "ASL", 30 | Opcode::BCC => "BCC", 31 | Opcode::BCS => "BCS", 32 | Opcode::BEQ => "BEQ", 33 | Opcode::BIT => "BIT", 34 | Opcode::BMI => "BMI", 35 | Opcode::BNE => "BNE", 36 | Opcode::BPL => "BPL", 37 | Opcode::BRK => "BRK", 38 | Opcode::BVC => "BVC", 39 | Opcode::BVS => "BVS", 40 | Opcode::CLC => "CLC", 41 | Opcode::CLD => "CLD", 42 | Opcode::CLI => "CLI", 43 | Opcode::CLV => "CLV", 44 | Opcode::CMP => "CMP", 45 | Opcode::CPX => "CPX", 46 | Opcode::CPY => "CPY", 47 | Opcode::DEC => "DEC", 48 | Opcode::DEX => "DEX", 49 | Opcode::DEY => "DEY", 50 | Opcode::EOR => "EOR", 51 | Opcode::ERR => "???", 52 | Opcode::INC => "INC", 53 | Opcode::INX => "INX", 54 | Opcode::INY => "INY", 55 | Opcode::JMP => "JMP", 56 | Opcode::JSR => "JSR", 57 | Opcode::LDA => "LDA", 58 | Opcode::LDX => "LDX", 59 | Opcode::LDY => "LDY", 60 | Opcode::LSR => "LSR", 61 | Opcode::NOP => "NOP", 62 | Opcode::ORA => "ORA", 63 | Opcode::PHA => "PHA", 64 | Opcode::PHP => "PHP", 65 | Opcode::PLA => "PLA", 66 | Opcode::PLP => "PLP", 67 | Opcode::ROL => "ROL", 68 | Opcode::ROR => "ROR", 69 | Opcode::RTI => "RTI", 70 | Opcode::RTS => "RTS", 71 | Opcode::SBC => "SBC", 72 | Opcode::SEC => "SEC", 73 | Opcode::SED => "SED", 74 | Opcode::SEI => "SEI", 75 | Opcode::STA => "STA", 76 | Opcode::STX => "STX", 77 | Opcode::STY => "STY", 78 | Opcode::TAX => "TAX", 79 | Opcode::TAY => "TAY", 80 | Opcode::TSX => "TSX", 81 | Opcode::TXA => "TXA", 82 | Opcode::TXS => "TXS", 83 | Opcode::TYA => "TYA", 84 | }; 85 | write!(f, "{}", name) 86 | } 87 | } 88 | 89 | #[allow(dead_code)] 90 | pub fn disasm(out: &mut impl Write, bus: &mut Bus, addr: u16) { 91 | let ins_code = bus.cpu_read(addr); 92 | 93 | let ins = &INSTRUCTION_LOOKUP[ins_code as usize]; 94 | 95 | let ins_size = get_instruction_size(ins); 96 | for i in 0..4 { 97 | if i < ins_size { 98 | write!(out, "{:02X} ", bus.cpu_read(addr + i)).unwrap(); 99 | } else { 100 | write!(out, " ").unwrap(); 101 | } 102 | } 103 | 104 | write!(out, "{} ", ins.opcode).unwrap(); 105 | } 106 | -------------------------------------------------------------------------------- /src/nes/dma.rs: -------------------------------------------------------------------------------- 1 | use super::bus::CpuBusDevice; 2 | use super::cpu::Cpu; 3 | use super::ppu::Ppu; 4 | use std::ops::Range; 5 | 6 | pub struct DmaDevice { 7 | pub page: u8, 8 | pub addr: u8, 9 | pub data: u8, 10 | pub flag: bool, 11 | pub transfer: bool, 12 | } 13 | 14 | impl DmaDevice { 15 | pub fn new() -> Self { 16 | DmaDevice { 17 | page: 0, 18 | addr: 0, 19 | data: 0, 20 | flag: true, 21 | transfer: false, 22 | } 23 | } 24 | 25 | pub fn reset(&mut self) { 26 | *self = Self::new(); 27 | } 28 | 29 | pub fn clock(&mut self, clock: i32, cpu: &mut Cpu, ppu: &mut Ppu) { 30 | if self.flag { 31 | if clock % 2 == 1 { 32 | self.flag = false; 33 | } 34 | } else { 35 | if clock % 2 == 0 { 36 | self.data = cpu.bus.cpu_read((self.page as u16) << 8 | (self.addr as u16)); 37 | } else { 38 | ppu.write_oam(self.addr, self.data); 39 | self.addr = self.addr.wrapping_add(1); 40 | if self.addr == 0x00 { 41 | self.transfer = false; 42 | self.flag = true; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | impl CpuBusDevice for DmaDevice { 50 | fn get_addr_range(&self) -> &Range { 51 | &(0x4014..0x4015) 52 | } 53 | 54 | fn cpu_write(&mut self, _: u16, data: u8) { 55 | self.page = data; 56 | self.addr = 0x00; 57 | self.transfer = true; 58 | } 59 | 60 | fn cpu_read(&mut self, _: u16) -> u8 { 61 | 0 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/nes/logger.rs: -------------------------------------------------------------------------------- 1 | use super::bus::CpuBusDevice; 2 | use std::ops::Range; 3 | 4 | const ADDR_RANGE: Range = 0x6000..0x7000; 5 | 6 | pub struct Logger { 7 | pub bytes: Vec, 8 | } 9 | 10 | impl CpuBusDevice for Logger { 11 | fn get_addr_range(&self) -> &Range { 12 | &ADDR_RANGE 13 | } 14 | 15 | fn cpu_write(&mut self, addr: u16, data: u8) { 16 | self.bytes[(addr - 0x6000) as usize] = data; 17 | } 18 | 19 | fn cpu_read(&mut self, _: u16) -> u8 { 20 | 0 21 | } 22 | } 23 | 24 | impl Logger { 25 | pub fn new() -> Self { 26 | Logger { 27 | bytes: vec![0; ADDR_RANGE.len() as usize], 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/nes/mappers.rs: -------------------------------------------------------------------------------- 1 | pub trait Mapper { 2 | fn map_write(&mut self, addr: u16, data: u8) -> u16; 3 | fn map_read(&mut self, addr: u16) -> u16; 4 | fn map_ppu_write(&mut self, addr: u16) -> u16; 5 | fn map_ppu_read(&mut self, addr: u16) -> u16; 6 | } 7 | 8 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 9 | 10 | pub struct Mapper0 { 11 | one_bank: bool, 12 | } 13 | 14 | impl Mapper for Mapper0 { 15 | fn map_write(&mut self, _: u16, _: u8) -> u16 { 16 | panic!("write is not supported"); 17 | } 18 | 19 | fn map_read(&mut self, addr: u16) -> u16 { 20 | let mut mapped_addr = addr - 0x8000; 21 | if self.one_bank { 22 | mapped_addr &= 0x3fff; 23 | } 24 | mapped_addr 25 | } 26 | 27 | fn map_ppu_write(&mut self, addr: u16) -> u16 { 28 | addr 29 | } 30 | 31 | fn map_ppu_read(&mut self, addr: u16) -> u16 { 32 | addr 33 | } 34 | } 35 | 36 | impl Mapper0 { 37 | pub fn new(rom_pages: u8) -> Self { 38 | Self { 39 | one_bank: rom_pages == 1, 40 | } 41 | } 42 | } 43 | 44 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | pub struct Mapper3 { 47 | one_bank: bool, 48 | bank_select: u16, 49 | } 50 | 51 | impl Mapper for Mapper3 { 52 | fn map_write(&mut self, addr: u16, data: u8) -> u16 { 53 | self.bank_select = (data & 0x03) as u16; 54 | addr 55 | } 56 | 57 | fn map_read(&mut self, addr: u16) -> u16 { 58 | let mut mapped_addr = addr - 0x8000; 59 | if self.one_bank { 60 | mapped_addr &= 0x3fff; 61 | } 62 | mapped_addr 63 | } 64 | 65 | fn map_ppu_write(&mut self, addr: u16) -> u16 { 66 | addr 67 | } 68 | 69 | fn map_ppu_read(&mut self, addr: u16) -> u16 { 70 | addr | (self.bank_select << 13) 71 | } 72 | } 73 | 74 | impl Mapper3 { 75 | pub fn new(rom_pages: u8) -> Self { 76 | Self { 77 | one_bank: rom_pages == 1, 78 | bank_select: 0, 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/nes/mod.rs: -------------------------------------------------------------------------------- 1 | // use rand::rngs::ThreadRng; 2 | // use rand::Rng; 3 | use std::path::PathBuf; 4 | 5 | use std::cell::RefCell; 6 | // use std::fs::File; 7 | use std::io::Write; 8 | use std::rc::Rc; 9 | 10 | pub mod bus; 11 | pub mod cartridge; 12 | pub mod controller; 13 | pub mod cpu; 14 | pub mod disasm; 15 | pub mod dma; 16 | pub mod logger; 17 | pub mod mappers; 18 | pub mod ppu; 19 | pub mod ram; 20 | 21 | use cartridge::Cartridge; 22 | use controller::Controller; 23 | use cpu::Cpu; 24 | use cpu::{to_u16, AddressingMode, INSTRUCTION_LOOKUP}; 25 | use dma::DmaDevice; 26 | use logger::Logger; 27 | use ppu::Ppu; 28 | use ram::Ram; 29 | 30 | #[derive(Default)] 31 | pub struct FrameTime { 32 | pub dt: f32, 33 | pub dt_accum: f32, 34 | pub fps: f32, 35 | } 36 | 37 | /// NES main emulator 38 | pub struct Emulator { 39 | pub cpu: Cpu, 40 | pub ppu: Rc>, 41 | pub ram: Rc>, 42 | pub cartridge: Rc>, 43 | pub logger: Rc>, 44 | pub clock: i32, 45 | pub dma: Rc>, 46 | pub controllers: [Rc>; 2], 47 | pub rom_loaded: bool, 48 | pub frame_time: FrameTime, 49 | } 50 | 51 | impl Emulator { 52 | pub fn new() -> Self { 53 | let mut cpu = Cpu::new(); 54 | 55 | let ram = Rc::new(RefCell::new(Ram::new())); 56 | let cartridge = Rc::new(RefCell::new(Cartridge::new())); 57 | let logger = Rc::new(RefCell::new(Logger::new())); 58 | let ppu = Rc::new(RefCell::new(Ppu::new(cartridge.clone()))); 59 | let dma = Rc::new(RefCell::new(DmaDevice::new())); 60 | let controller0 = Rc::new(RefCell::new(Controller::new(0))); 61 | let controller1 = Rc::new(RefCell::new(Controller::new(1))); 62 | 63 | cpu.bus.connect(ram.clone()); 64 | cpu.bus.connect(cartridge.clone()); 65 | cpu.bus.connect(logger.clone()); 66 | cpu.bus.connect(ppu.clone()); 67 | cpu.bus.connect(dma.clone()); 68 | cpu.bus.connect(controller0.clone()); 69 | cpu.bus.connect(controller1.clone()); 70 | 71 | Emulator { 72 | cpu, 73 | ppu, 74 | ram, 75 | cartridge, 76 | logger, 77 | clock: 0, 78 | dma, 79 | controllers: [controller0, controller1], 80 | rom_loaded: false, 81 | frame_time: FrameTime::default(), 82 | } 83 | } 84 | 85 | pub fn load_rom(&mut self, romfile: &PathBuf) { 86 | self.cartridge.borrow_mut().load_from_file(romfile); 87 | self.cpu.reset(); 88 | self.ppu.borrow_mut().reset(); 89 | self.dma.borrow_mut().reset(); 90 | self.clock = 1; 91 | self.rom_loaded = true; 92 | } 93 | 94 | #[allow(dead_code)] 95 | pub fn write_state(&mut self, f: &mut impl Write) { 96 | write!(f, "{:04X} ", self.cpu.PC).unwrap(); 97 | disasm::disasm(f, &mut self.cpu.bus, self.cpu.PC); 98 | 99 | let ins_code = self.cpu.bus.cpu_read(self.cpu.PC); 100 | let ins = &INSTRUCTION_LOOKUP[ins_code as usize]; 101 | let mut addr_str = String::new(); 102 | match ins.mode { 103 | // JMP ABS 104 | AddressingMode::ABS => { 105 | addr_str = format!( 106 | "${:04X} ", 107 | to_u16( 108 | self.cpu.bus.cpu_read(self.cpu.PC + 2), 109 | self.cpu.bus.cpu_read(self.cpu.PC + 1) 110 | ) 111 | ); 112 | } 113 | AddressingMode::ABX => { 114 | addr_str = format!( 115 | "${:04X},X ", 116 | to_u16( 117 | self.cpu.bus.cpu_read(self.cpu.PC + 2), 118 | self.cpu.bus.cpu_read(self.cpu.PC + 1) 119 | ) 120 | ); 121 | } 122 | AddressingMode::ABY => { 123 | addr_str = format!( 124 | "${:04X},Y ", 125 | to_u16( 126 | self.cpu.bus.cpu_read(self.cpu.PC + 2), 127 | self.cpu.bus.cpu_read(self.cpu.PC + 1) 128 | ) 129 | ); 130 | } 131 | AddressingMode::REL => { 132 | addr_str = format!( 133 | "${:04X} ", 134 | self.cpu.PC + self.cpu.bus.cpu_read(self.cpu.PC + 1) as u16 + 2 135 | ); 136 | } 137 | AddressingMode::IND => { 138 | addr_str = format!( 139 | "(${:04X}) ", 140 | to_u16( 141 | self.cpu.bus.cpu_read(self.cpu.PC + 2), 142 | self.cpu.bus.cpu_read(self.cpu.PC + 1) 143 | ) 144 | ); 145 | } 146 | AddressingMode::IMP => {} 147 | AddressingMode::ACC => { 148 | addr_str = format!("A "); 149 | } 150 | AddressingMode::IMM => { 151 | addr_str = format!("#${:02X} ", self.cpu.bus.cpu_read(self.cpu.PC + 1)); 152 | } 153 | AddressingMode::ZP0 => { 154 | addr_str = format!("${:02X} ", self.cpu.bus.cpu_read(self.cpu.PC + 1)); 155 | } 156 | AddressingMode::IZX => { 157 | addr_str = format!("(${:02X},X) ", self.cpu.bus.cpu_read(self.cpu.PC + 1)); 158 | } 159 | AddressingMode::IZY => { 160 | addr_str = format!("(${:02X}),Y ", self.cpu.bus.cpu_read(self.cpu.PC + 1)); 161 | } 162 | AddressingMode::ZPX => { 163 | addr_str = format!("${:02X},X ", self.cpu.bus.cpu_read(self.cpu.PC + 1)); 164 | } 165 | AddressingMode::ZPY => { 166 | addr_str = format!("${:02X},Y ", self.cpu.bus.cpu_read(self.cpu.PC + 1)); 167 | } 168 | } 169 | write!(f, "{:<20}", addr_str).unwrap(); 170 | 171 | write!(f, "A:{:02X} ", self.cpu.A).unwrap(); 172 | write!(f, "X:{:02X} ", self.cpu.X).unwrap(); 173 | write!(f, "Y:{:02X} ", self.cpu.Y).unwrap(); 174 | 175 | write!(f, "P:{:02X} ", self.cpu.flags.to_byte()).unwrap(); 176 | write!(f, "SP:{:02X} ", self.cpu.SP).unwrap(); 177 | write!( 178 | f, 179 | "PPU:{:>3},{:>3} ", 180 | self.ppu.borrow().scanline, 181 | self.ppu.borrow().cycle 182 | ) 183 | .unwrap(); 184 | write!(f, "CYC:{} ", self.cpu.total_cycles).unwrap(); 185 | 186 | f.write_all(b"\n").unwrap(); 187 | } 188 | 189 | pub fn update(&mut self, dt: f32) { 190 | if !self.rom_loaded { 191 | return; 192 | } 193 | 194 | // limit fps 195 | if !self.frame_time.update(dt) { 196 | return; 197 | } 198 | 199 | while !self.ppu.borrow().screen.complete { 200 | self.clock(); 201 | } 202 | } 203 | 204 | pub fn clock(&mut self) { 205 | self.ppu.borrow_mut().clock(); 206 | 207 | if self.clock % 3 == 0 { 208 | if self.dma.borrow_mut().transfer { 209 | self.dma 210 | .borrow_mut() 211 | .clock(self.clock, &mut self.cpu, &mut self.ppu.borrow_mut()); 212 | } else { 213 | self.cpu.clock(); 214 | } 215 | } 216 | 217 | if self.ppu.borrow().nmi { 218 | self.ppu.borrow_mut().nmi = false; 219 | self.cpu.nmi(); 220 | } 221 | 222 | self.clock += 1; 223 | } 224 | } 225 | 226 | impl FrameTime { 227 | pub fn update(&mut self, dt: f32) -> bool { 228 | // Limit to 60 fps 229 | self.dt_accum += dt; 230 | self.dt += dt; 231 | const FRAME_TIME: f32 = 1.0 / 60.0; 232 | if self.dt_accum < FRAME_TIME { 233 | return false; 234 | } 235 | self.fps = 1.0 / self.dt; 236 | self.dt_accum -= FRAME_TIME; 237 | self.dt = 0.0; 238 | true 239 | } 240 | } 241 | 242 | #[cfg(test)] 243 | mod tests { 244 | use super::*; 245 | use std::fs::File; 246 | use std::io::prelude::*; 247 | use std::io::BufReader; 248 | use std::path::PathBuf; 249 | 250 | #[test] 251 | fn cpu_test() { 252 | let mut e = Emulator::new(); 253 | 254 | let mut buf = Vec::new(); 255 | 256 | let log_file = File::create(&PathBuf::from("nestest_out.log")).unwrap(); 257 | 258 | e.load_rom(&PathBuf::from("roms/nestest.nes")); 259 | e.cpu.PC = 0xC000; 260 | let mut cmp_file = BufReader::new(File::open(&PathBuf::from("roms/nestest.log")).unwrap()); 261 | for _ in 1..100000 { 262 | // After that some NOP operations comes in which we don't support 263 | if e.cpu.total_cycles >= 14579 { 264 | break; 265 | } 266 | 267 | if e.cpu.cycles == 0 { 268 | buf.clear(); 269 | e.write_state(&mut buf); 270 | let out_line = String::from_utf8_lossy(&buf); 271 | write!(&log_file, "{}", out_line).unwrap(); 272 | 273 | let mut cmp_line = String::new(); 274 | cmp_file.read_line(&mut cmp_line).unwrap(); 275 | 276 | // ignore whitespace 277 | let out_vec: Vec<&str> = out_line.split_whitespace().collect(); 278 | let mut cmp_vec: Vec<&str> = cmp_line.split_whitespace().collect(); 279 | 280 | // filter out some data we don't support: 281 | // find range from "=" to "A:" and remove 282 | let i1 = cmp_vec.iter().position(|&r| r.starts_with("A:")).unwrap(); 283 | let p0 = cmp_vec 284 | .iter() 285 | .take(i1 + 1) 286 | .position(|&r| r.starts_with("@")); 287 | let p1 = cmp_vec 288 | .iter() 289 | .take(i1 + 1) 290 | .position(|&r| r.starts_with("=")); 291 | let p = std::cmp::min(p0.unwrap_or(i1), p1.unwrap_or(i1)); 292 | if p < i1 { 293 | cmp_vec.drain(p..i1); 294 | } 295 | 296 | assert_eq!(out_vec, cmp_vec); 297 | } 298 | 299 | e.clock(); 300 | e.clock(); 301 | e.clock(); 302 | } 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/nes/ppu.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::ops::Range; 3 | use std::rc::Rc; 4 | 5 | use super::bus::CpuBusDevice; 6 | use super::cartridge::Cartridge; 7 | 8 | #[derive(Default, Clone)] 9 | pub struct CtrlReg { 10 | pub nametable_x: bool, // 0 11 | pub nametable_y: bool, // 1 12 | pub increment: bool, // 2 13 | pub pattern_sprite: bool, // 3 14 | pub pattern_background: bool, // 4 15 | pub is_wide_sprite: bool, // 5 16 | pub master_slave: bool, // 6 17 | pub generate_nmi: bool, // 7 18 | } 19 | 20 | impl CtrlReg { 21 | pub fn set_byte(&mut self, b: u8) { 22 | self.nametable_x = ((b >> 0) & 1) != 0; 23 | self.nametable_y = ((b >> 1) & 1) != 0; 24 | self.increment = ((b >> 2) & 1) != 0; 25 | self.pattern_sprite = ((b >> 3) & 1) != 0; 26 | self.pattern_background = ((b >> 4) & 1) != 0; 27 | self.is_wide_sprite = ((b >> 5) & 1) != 0; 28 | self.master_slave = ((b >> 6) & 1) != 0; 29 | self.generate_nmi = ((b >> 7) & 1) != 0; 30 | } 31 | } 32 | 33 | #[derive(Default, Clone)] 34 | pub struct MaskReg { 35 | pub grayscale: bool, // 0 36 | pub show_background_left: bool, // 1 37 | pub show_sprites_left: bool, // 2 38 | pub show_background: bool, // 3 39 | pub show_sprites: bool, // 4 40 | pub emphasize_red: bool, // 5 41 | pub emphasize_green: bool, // 6 42 | pub emphasize_blue: bool, // 7 43 | } 44 | 45 | impl MaskReg { 46 | pub fn set_byte(&mut self, b: u8) { 47 | self.grayscale = ((b >> 0) & 1) != 0; 48 | self.show_background_left = ((b >> 1) & 1) != 0; 49 | self.show_sprites_left = ((b >> 2) & 1) != 0; 50 | self.show_background = ((b >> 3) & 1) != 0; 51 | self.show_sprites = ((b >> 4) & 1) != 0; 52 | self.emphasize_red = ((b >> 5) & 1) != 0; 53 | self.emphasize_green = ((b >> 6) & 1) != 0; 54 | self.emphasize_blue = ((b >> 7) & 1) != 0; 55 | } 56 | } 57 | 58 | #[derive(Default, Clone)] 59 | pub struct StatusReg { 60 | pub unused: u8, // 5 bits 61 | pub sprite_overflow: bool, // 5 62 | pub sprite_zero_hit: bool, // 6 63 | pub vertical_blank: bool, // 7 64 | } 65 | 66 | impl StatusReg { 67 | pub fn to_byte(&self) -> u8 { 68 | self.unused 69 | | (self.sprite_overflow as u8) << 5 70 | | (self.sprite_zero_hit as u8) << 6 71 | | (self.vertical_blank as u8) << 7 72 | } 73 | } 74 | 75 | #[derive(Default, Clone)] 76 | pub struct LoopyAddr { 77 | pub coarse_x: u8, // 5 bits 78 | pub coarse_y: u8, // 5 bits 79 | pub nametable_x: bool, 80 | pub nametable_y: bool, 81 | pub fine_y: u8, // 3 bits 82 | pub unused: bool, 83 | } 84 | 85 | impl LoopyAddr { 86 | pub fn set_data(&mut self, data: u16) { 87 | self.coarse_x = (data as u8) & 0b00011111; 88 | self.coarse_y = ((data >> 5) as u8) & 0b00011111; 89 | self.nametable_x = ((data >> 10) & 1) != 0; 90 | self.nametable_y = ((data >> 11) & 1) != 0; 91 | self.fine_y = ((data >> 12) as u8) & 0b00000111; 92 | self.unused = ((data >> 15) & 1) != 0; 93 | } 94 | 95 | pub fn to_data(&self) -> u16 { 96 | ((self.coarse_x & 0b00011111) as u16) << 0 97 | | ((self.coarse_y & 0b00011111) as u16) << 5 98 | | (self.nametable_x as u16) << 10 99 | | (self.nametable_y as u16) << 11 100 | | ((self.fine_y & 0b00000111) as u16) << 12 101 | | (self.unused as u16) << 15 102 | } 103 | } 104 | 105 | const PALETTE: [u32; 64] = [ 106 | 0x7C7C7C, 0x0000FC, 0x0000BC, 0x4428BC, 0x940084, 0xA80020, 0xA81000, 0x881400, 0x503000, 107 | 0x007800, 0x006800, 0x005800, 0x004058, 0x000000, 0x000000, 0x000000, 0xBCBCBC, 0x0078F8, 108 | 0x0058F8, 0x6844FC, 0xD800CC, 0xE40058, 0xF83800, 0xE45C10, 0xAC7C00, 0x00B800, 0x00A800, 109 | 0x00A844, 0x008888, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0x3CBCFC, 0x6888FC, 0x9878F8, 110 | 0xF878F8, 0xF85898, 0xF87858, 0xFCA044, 0xF8B800, 0xB8F818, 0x58D854, 0x58F898, 0x00E8D8, 111 | 0x787878, 0x000000, 0x000000, 0xFCFCFC, 0xA4E4FC, 0xB8B8F8, 0xD8B8F8, 0xF8B8F8, 0xF8A4C0, 112 | 0xF0D0B0, 0xFCE0A8, 0xF8D878, 0xD8F878, 0xB8F8B8, 0xB8F8D8, 0x00FCFC, 0xF8D8F8, 0x000000, 113 | 0x000000, 114 | ]; 115 | 116 | #[derive(Default, Clone, Copy)] 117 | pub struct ObjectAttributeEntry { 118 | pub y: u8, 119 | pub id: u8, 120 | pub attr: u8, 121 | pub x: u8, 122 | } 123 | 124 | #[derive(Default, Clone, Copy)] 125 | pub struct SpriteRenderState { 126 | pub scanline: [ObjectAttributeEntry; 8], 127 | count: u8, 128 | shifter_pattern_lo: [u8; 8], 129 | shifter_pattern_hi: [u8; 8], 130 | } 131 | 132 | #[derive(Default, Clone, Copy)] 133 | pub struct BgRenderState { 134 | tile_id: u8, 135 | tile_attrib: u8, 136 | tile_lsb: u8, 137 | tile_msb: u8, 138 | shifter_pattern_lo: u16, 139 | shifter_pattern_hi: u16, 140 | shifter_attrib_lo: u16, 141 | shifter_attrib_hi: u16, 142 | } 143 | 144 | pub const SCREEN_SIZE: (usize, usize) = (256, 240); 145 | 146 | /// Screen buffer. 147 | pub struct Screen { 148 | buffer: Vec, 149 | pub complete: bool, 150 | } 151 | 152 | impl Default for Screen { 153 | fn default() -> Self { 154 | Screen { 155 | buffer: vec![0; SCREEN_SIZE.0 * SCREEN_SIZE.1], 156 | complete: true, 157 | } 158 | } 159 | } 160 | 161 | impl Screen { 162 | pub fn set_pixel(&mut self, x: usize, y: usize, c: u32) { 163 | self.buffer[x + y * SCREEN_SIZE.0] = c; 164 | } 165 | 166 | pub fn get_pixel(&self, x: usize, y: usize) -> u32 { 167 | self.buffer[x + y * SCREEN_SIZE.0] 168 | } 169 | } 170 | 171 | pub struct Ppu { 172 | pub cartridge: Rc>, 173 | pub screen: Screen, 174 | pub cycle: i16, 175 | pub scanline: i16, 176 | pub odd_frame: bool, 177 | 178 | pub ctrl: CtrlReg, 179 | pub mask: MaskReg, 180 | pub status: StatusReg, 181 | 182 | pub nmi: bool, 183 | 184 | pub oam_addr: u8, 185 | pub oam_mem: [ObjectAttributeEntry; 64], 186 | 187 | pub name_table: [[u8; 1024]; 2], 188 | pub pal_table: [u8; 32], 189 | 190 | pub bg_state: BgRenderState, 191 | pub sprite_state: SpriteRenderState, 192 | 193 | pub sprite_zero_hit_possible: bool, 194 | pub sprite_zero_being_rendered: bool, 195 | 196 | // Loopy: 197 | pub loopy_latch: bool, 198 | pub ppu_data_buf: u8, 199 | pub vram_addr: LoopyAddr, 200 | pub tram_addr: LoopyAddr, 201 | 202 | pub fine_x: u8, 203 | } 204 | 205 | impl CpuBusDevice for Ppu { 206 | fn get_addr_range(&self) -> &Range { 207 | &(0x2000..0x3FFF) 208 | } 209 | 210 | fn cpu_write(&mut self, addr: u16, data: u8) { 211 | match addr & 0x0007 { 212 | 0x0000 => { 213 | self.ctrl.set_byte(data); 214 | self.tram_addr.nametable_x = self.ctrl.nametable_x; 215 | self.tram_addr.nametable_y = self.ctrl.nametable_y; 216 | } 217 | 0x0001 => { 218 | self.mask.set_byte(data); 219 | } 220 | 0x0002 => { 221 | // status 222 | } 223 | 0x0003 => { 224 | self.oam_addr = data; 225 | } 226 | 0x0004 => { 227 | self.write_oam(self.oam_addr, data); 228 | } 229 | 0x0005 => { 230 | // Scroll 231 | if self.loopy_latch == false { 232 | self.fine_x = data & 0x07; 233 | self.tram_addr.coarse_x = data >> 3; 234 | self.loopy_latch = true; 235 | } else { 236 | self.tram_addr.fine_y = data & 0x07; 237 | self.tram_addr.coarse_y = data >> 3; 238 | self.loopy_latch = false; 239 | } 240 | } 241 | 0x0006 => { 242 | // PPU Address 243 | if self.loopy_latch == false { 244 | self.tram_addr.set_data( 245 | (((data as u16) & 0x3F) << 8) | (self.tram_addr.to_data() & 0x00FF), 246 | ); 247 | self.loopy_latch = true; 248 | } else { 249 | self.tram_addr 250 | .set_data((self.tram_addr.to_data() & 0xFF00) | (data as u16)); 251 | self.vram_addr = self.tram_addr.clone(); 252 | self.loopy_latch = false; 253 | } 254 | } 255 | 0x0007 => { 256 | // PPU Data 257 | 258 | self.ppu_write(self.vram_addr.to_data(), data); 259 | self.incr_vram_addr(); 260 | } 261 | _ => {} 262 | } 263 | } 264 | 265 | fn cpu_read(&mut self, addr: u16) -> u8 { 266 | let mut data: u8 = 0; 267 | 268 | match addr & 0x0007 { 269 | 0x0002 => { 270 | // Status 271 | data = (self.status.to_byte() & 0xE0) | (self.ppu_data_buf & 0x1F); 272 | self.status.vertical_blank = false; 273 | self.loopy_latch = false; 274 | } 275 | 0x0004 => { 276 | // OAM Data 277 | let index = (self.oam_addr / 4) as usize; 278 | data = { 279 | match self.oam_addr % 4 { 280 | 0 => self.oam_mem[index].y, 281 | 1 => self.oam_mem[index].id, 282 | 2 => self.oam_mem[index].attr, 283 | 3 => self.oam_mem[index].x, 284 | _ => 0, 285 | } 286 | }; 287 | } 288 | 0x0007 => { 289 | // PPU Data 290 | data = self.ppu_data_buf; 291 | self.ppu_data_buf = self.ppu_read(self.vram_addr.to_data()); 292 | if self.vram_addr.to_data() >= 0x3F00 { 293 | data = self.ppu_data_buf; 294 | } 295 | self.incr_vram_addr(); 296 | } 297 | _ => {} 298 | } 299 | 300 | data 301 | } 302 | } 303 | 304 | impl BgRenderState { 305 | pub fn load_shifters(&mut self) { 306 | self.shifter_pattern_lo = (self.shifter_pattern_lo & 0xFF00) | (self.tile_lsb as u16); 307 | self.shifter_pattern_hi = (self.shifter_pattern_hi & 0xFF00) | (self.tile_msb as u16); 308 | self.shifter_attrib_lo = self.shifter_attrib_lo & 0xFF00; 309 | if self.tile_attrib & 0b01 != 0 { 310 | self.shifter_attrib_lo |= 0xFF; 311 | } 312 | self.shifter_attrib_hi = self.shifter_attrib_hi & 0xFF00; 313 | if self.tile_attrib & 0b10 != 0 { 314 | self.shifter_attrib_hi |= 0xFF; 315 | } 316 | } 317 | 318 | pub fn update_shifters(&mut self) { 319 | self.shifter_pattern_lo <<= 1; 320 | self.shifter_pattern_hi <<= 1; 321 | self.shifter_attrib_lo <<= 1; 322 | self.shifter_attrib_hi <<= 1; 323 | } 324 | } 325 | 326 | impl SpriteRenderState { 327 | pub fn update_shifters(&mut self) { 328 | for i in 0..self.count as usize { 329 | if self.scanline[i].x > 0 { 330 | self.scanline[i].x -= 1; 331 | } else { 332 | self.shifter_pattern_lo[i] <<= 1; 333 | self.shifter_pattern_hi[i] <<= 1; 334 | } 335 | } 336 | } 337 | } 338 | 339 | fn flipbyte(b: u8) -> u8 { 340 | let mut b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; 341 | b = (b & 0xCC) >> 2 | (b & 0x33) << 2; 342 | b = (b & 0xAA) >> 1 | (b & 0x55) << 1; 343 | b 344 | } 345 | 346 | impl Ppu { 347 | pub fn new(cartridge: Rc>) -> Self { 348 | Ppu { 349 | cartridge: cartridge, 350 | screen: Screen::default(), 351 | cycle: 0, 352 | scanline: 0, 353 | odd_frame: false, 354 | ctrl: CtrlReg::default(), 355 | mask: MaskReg::default(), 356 | status: StatusReg::default(), 357 | nmi: false, 358 | oam_addr: 0, 359 | oam_mem: [ObjectAttributeEntry::default(); 64], 360 | name_table: [[0; 1024]; 2], 361 | pal_table: [0; 32], 362 | bg_state: BgRenderState::default(), 363 | sprite_state: SpriteRenderState::default(), 364 | sprite_zero_hit_possible: false, 365 | sprite_zero_being_rendered: false, 366 | loopy_latch: false, 367 | ppu_data_buf: 0, 368 | vram_addr: LoopyAddr::default(), 369 | tram_addr: LoopyAddr::default(), 370 | fine_x: 0, 371 | } 372 | } 373 | 374 | pub fn reset(&mut self) { 375 | *self = Ppu::new(self.cartridge.clone()); 376 | } 377 | 378 | pub fn ppu_write(&mut self, addr: u16, data: u8) { 379 | let addr = addr & 0x3FFF; 380 | if addr < 0x2000 { 381 | self.cartridge.borrow_mut().ppu_write(addr, data); 382 | } else if addr <= 0x3EFF { 383 | let addr = addr & 0x0FFF; 384 | if self.cartridge.borrow_mut().is_vertical_mirror() { 385 | if addr <= 0x03FF || addr >= 0x0800 && addr <= 0x0BFF { 386 | self.name_table[0][(addr & 0x03FF) as usize] = data; 387 | } else { 388 | self.name_table[1][(addr & 0x03FF) as usize] = data; 389 | } 390 | } else { 391 | if addr <= 0x07FF { 392 | self.name_table[0][(addr & 0x03FF) as usize] = data; 393 | } else { 394 | self.name_table[1][(addr & 0x03FF) as usize] = data; 395 | } 396 | } 397 | } else if addr <= 0x3FFF { 398 | let mut addr = addr & 0x001F; 399 | addr = { 400 | match addr { 401 | 0x0010 => 0x0000, 402 | 0x0014 => 0x0004, 403 | 0x0018 => 0x0008, 404 | 0x001C => 0x000C, 405 | _ => addr, 406 | } 407 | }; 408 | self.pal_table[addr as usize] = data; 409 | } 410 | } 411 | 412 | pub fn ppu_read(&mut self, addr: u16) -> u8 { 413 | let mut data: u8 = 0; 414 | let addr = addr & 0x3FFF; 415 | 416 | if addr < 0x2000 { 417 | return self.cartridge.borrow_mut().ppu_read(addr); 418 | } 419 | 420 | if addr <= 0x3EFF { 421 | let addr = addr & 0x0FFF; 422 | if self.cartridge.borrow_mut().is_vertical_mirror() { 423 | if addr <= 0x03FF || addr >= 0x0800 && addr <= 0x0BFF { 424 | data = self.name_table[0][(addr & 0x03FF) as usize]; 425 | } else { 426 | data = self.name_table[1][(addr & 0x03FF) as usize]; 427 | } 428 | } else { 429 | if addr <= 0x07FF { 430 | data = self.name_table[0][(addr & 0x03FF) as usize]; 431 | } else { 432 | data = self.name_table[1][(addr & 0x03FF) as usize]; 433 | } 434 | } 435 | } else if addr <= 0x3FFF { 436 | let mut addr = addr & 0x001F; 437 | addr = { 438 | match addr { 439 | 0x0010 => 0x0000, 440 | 0x0014 => 0x0004, 441 | 0x0018 => 0x0008, 442 | 0x001C => 0x000C, 443 | _ => addr, 444 | } 445 | }; 446 | data = self.pal_table[addr as usize] & (if self.mask.grayscale { 0x30 } else { 0x3F }); 447 | } 448 | 449 | data 450 | } 451 | 452 | pub fn write_oam(&mut self, addr: u8, data: u8) { 453 | let index = (addr / 4) as usize; 454 | match addr % 4 { 455 | 0 => { 456 | self.oam_mem[index].y = data; 457 | } 458 | 1 => { 459 | self.oam_mem[index].id = data; 460 | } 461 | 2 => { 462 | self.oam_mem[index].attr = data; 463 | } 464 | 3 => { 465 | self.oam_mem[index].x = data; 466 | } 467 | _ => {} 468 | } 469 | } 470 | 471 | fn incr_vram_addr(&mut self) { 472 | let mut reg = self.vram_addr.to_data(); 473 | reg = reg.wrapping_add(if self.ctrl.increment { 32 } else { 1 }); 474 | self.vram_addr.set_data(reg); 475 | } 476 | 477 | fn incr_scroll_x(&mut self) { 478 | if self.mask.show_background || self.mask.show_sprites { 479 | if self.vram_addr.coarse_x == 31 { 480 | self.vram_addr.coarse_x = 0; 481 | self.vram_addr.nametable_x = !self.vram_addr.nametable_x; 482 | } else { 483 | self.vram_addr.coarse_x += 1; 484 | } 485 | } 486 | } 487 | 488 | fn incr_scroll_y(&mut self) { 489 | if self.mask.show_background || self.mask.show_sprites { 490 | if self.vram_addr.fine_y < 7 { 491 | self.vram_addr.fine_y += 1; 492 | } else { 493 | self.vram_addr.fine_y = 0; 494 | if self.vram_addr.coarse_y == 29 { 495 | self.vram_addr.coarse_y = 0; 496 | self.vram_addr.nametable_y = !self.vram_addr.nametable_y; 497 | } else if self.vram_addr.coarse_y == 31 { 498 | self.vram_addr.coarse_y = 0; 499 | } else { 500 | self.vram_addr.coarse_y += 1; 501 | } 502 | } 503 | } 504 | } 505 | 506 | fn transfer_addr_x(&mut self) { 507 | if self.mask.show_background || self.mask.show_sprites { 508 | self.vram_addr.nametable_x = self.tram_addr.nametable_x; 509 | self.vram_addr.coarse_x = self.tram_addr.coarse_x; 510 | } 511 | } 512 | 513 | fn transfer_addr_y(&mut self) { 514 | if self.mask.show_background || self.mask.show_sprites { 515 | self.vram_addr.fine_y = self.tram_addr.fine_y; 516 | self.vram_addr.nametable_y = self.tram_addr.nametable_y; 517 | self.vram_addr.coarse_y = self.tram_addr.coarse_y; 518 | } 519 | } 520 | 521 | fn update_shifters(&mut self) { 522 | if self.mask.show_background { 523 | self.bg_state.update_shifters(); 524 | } 525 | 526 | if self.mask.show_sprites && self.cycle >= 1 && self.cycle < 258 { 527 | self.sprite_state.update_shifters(); 528 | } 529 | } 530 | 531 | pub fn clock(&mut self) { 532 | if self.scanline >= -1 && self.scanline < 240 { 533 | if self.scanline == 0 534 | && self.cycle == 0 535 | && self.odd_frame 536 | && (self.mask.show_background || self.mask.show_sprites) 537 | { 538 | // odd frame cycle skip 539 | self.cycle = 1; 540 | } 541 | 542 | if self.scanline == -1 && self.cycle == 1 { 543 | // New frame 544 | self.status.vertical_blank = false; 545 | self.status.sprite_overflow = false; 546 | self.status.sprite_zero_hit = false; 547 | for i in 0..8 { 548 | self.sprite_state.shifter_pattern_lo[i] = 0; 549 | self.sprite_state.shifter_pattern_hi[i] = 0; 550 | } 551 | } 552 | 553 | if (self.cycle >= 2 && self.cycle < 258) || (self.cycle >= 321 && self.cycle < 338) { 554 | self.update_shifters(); 555 | 556 | match (self.cycle - 1) % 8 { 557 | 0 => { 558 | self.bg_state.load_shifters(); 559 | 560 | self.bg_state.tile_id = 561 | self.ppu_read(0x2000 | (self.vram_addr.to_data() & 0x0FFF)); 562 | } 563 | 2 => { 564 | self.bg_state.tile_attrib = self.ppu_read( 565 | 0x23C0 566 | | ((self.vram_addr.nametable_y as u16) << 11) 567 | | ((self.vram_addr.nametable_x as u16) << 10) 568 | | (((self.vram_addr.coarse_y as u16) >> 2) << 3) 569 | | ((self.vram_addr.coarse_x as u16) >> 2), 570 | ); 571 | 572 | if self.vram_addr.coarse_y & 0x02 != 0 { 573 | self.bg_state.tile_attrib >>= 4; 574 | } 575 | if self.vram_addr.coarse_x & 0x02 != 0 { 576 | self.bg_state.tile_attrib >>= 2; 577 | } 578 | self.bg_state.tile_attrib &= 0x03; 579 | } 580 | 4 => { 581 | self.bg_state.tile_lsb = self.ppu_read( 582 | ((self.ctrl.pattern_background as u16) << 12) 583 | + ((self.bg_state.tile_id as u16) << 4) 584 | + ((self.vram_addr.fine_y as u16) + 0), 585 | ); 586 | } 587 | 6 => { 588 | self.bg_state.tile_msb = self.ppu_read( 589 | ((self.ctrl.pattern_background as u16) << 12) 590 | + ((self.bg_state.tile_id as u16) << 4) 591 | + ((self.vram_addr.fine_y as u16) + 8), 592 | ); 593 | } 594 | 7 => { 595 | self.incr_scroll_x(); 596 | } 597 | _ => {} 598 | } 599 | } 600 | 601 | if self.cycle == 256 { 602 | self.incr_scroll_y(); 603 | } 604 | 605 | if self.cycle == 257 { 606 | self.bg_state.load_shifters(); 607 | self.transfer_addr_x(); 608 | } 609 | 610 | if self.cycle == 338 || self.cycle == 340 { 611 | self.bg_state.tile_id = self.ppu_read(0x2000 | (self.vram_addr.to_data() & 0x0FFF)); 612 | } 613 | 614 | if self.scanline == -1 && self.cycle >= 280 && self.cycle < 305 { 615 | self.transfer_addr_y(); 616 | } 617 | 618 | // Sprite Rendering 619 | if self.cycle == 257 && self.scanline >= 0 { 620 | self.sprite_state.scanline = [ObjectAttributeEntry::default(); 8]; 621 | self.sprite_state.count = 0; 622 | 623 | for i in 0..8 { 624 | self.sprite_state.shifter_pattern_lo[i] = 0; 625 | self.sprite_state.shifter_pattern_hi[i] = 0; 626 | } 627 | 628 | let mut oam_index: u8 = 0; 629 | self.sprite_zero_hit_possible = false; 630 | 631 | while oam_index < 64 && self.sprite_state.count < 9 { 632 | let diff = (self.scanline as i16) - (self.oam_mem[oam_index as usize].y as i16); 633 | 634 | let sprite_size = if self.ctrl.is_wide_sprite { 16 } else { 8 }; 635 | if diff >= 0 && diff < sprite_size && self.sprite_state.count < 8 { 636 | if self.sprite_state.count < 8 { 637 | if oam_index == 0 { 638 | self.sprite_zero_hit_possible = true; 639 | } 640 | 641 | self.sprite_state.scanline[self.sprite_state.count as usize] = 642 | self.oam_mem[oam_index as usize]; 643 | } 644 | self.sprite_state.count += 1; 645 | } 646 | oam_index += 1; 647 | } 648 | 649 | self.status.sprite_overflow = self.sprite_state.count >= 8; 650 | } 651 | 652 | if self.cycle == 340 { 653 | for i in 0..self.sprite_state.count as usize { 654 | let mut sprite_pattern_addr_lo: u16; 655 | let sprite_pattern_addr_hi: u16; 656 | 657 | let scan_id = self.sprite_state.scanline[i].id as u16; 658 | let scan_y = self.scanline - self.sprite_state.scanline[i].y as i16; 659 | 660 | if !self.ctrl.is_wide_sprite { 661 | // 8x8 sprite 662 | if self.sprite_state.scanline[i].attr & 0x80 == 0 { 663 | sprite_pattern_addr_lo = ((self.ctrl.pattern_sprite as u16) << 12) 664 | | (scan_id << 4) 665 | | (scan_y as u16); 666 | } else { 667 | sprite_pattern_addr_lo = ((self.ctrl.pattern_sprite as u16) << 12) 668 | | (scan_id << 4) 669 | | ((7 - scan_y) as u16); 670 | } 671 | } else { 672 | // 8x16 sprite 673 | sprite_pattern_addr_lo = ((scan_id & 0x01) << 12) | ((scan_id & 0xFE) << 4); 674 | 675 | if self.sprite_state.scanline[i].attr & 0x80 != 0 { 676 | if scan_y < 8 { 677 | sprite_pattern_addr_lo |= (scan_y as u16) & 0x07; 678 | } else { 679 | sprite_pattern_addr_lo |= (scan_y as u16) & 0x07; 680 | } 681 | } else { 682 | if scan_y < 8 { 683 | sprite_pattern_addr_lo |= (7 - scan_y) as u16 & 0x07; 684 | } else { 685 | sprite_pattern_addr_lo |= (7 - scan_y) as u16 & 0x07; 686 | } 687 | } 688 | } 689 | 690 | sprite_pattern_addr_hi = sprite_pattern_addr_lo + 8; 691 | 692 | let mut sprite_pattern_bits_lo = self.ppu_read(sprite_pattern_addr_lo); 693 | let mut sprite_pattern_bits_hi = self.ppu_read(sprite_pattern_addr_hi); 694 | 695 | if self.sprite_state.scanline[i].attr & 0x40 != 0 { 696 | sprite_pattern_bits_lo = flipbyte(sprite_pattern_bits_lo); 697 | sprite_pattern_bits_hi = flipbyte(sprite_pattern_bits_hi); 698 | } 699 | 700 | self.sprite_state.shifter_pattern_lo[i] = sprite_pattern_bits_lo; 701 | self.sprite_state.shifter_pattern_hi[i] = sprite_pattern_bits_hi; 702 | } 703 | } 704 | } 705 | 706 | if self.scanline >= 241 && self.scanline < 261 { 707 | if self.scanline == 241 && self.cycle == 1 { 708 | self.status.vertical_blank = true; 709 | 710 | if self.ctrl.generate_nmi { 711 | self.nmi = true; 712 | } 713 | } 714 | } 715 | 716 | // Compose! 717 | 718 | // BG 719 | let mut bg_pixel: u8 = 0x00; 720 | let mut bg_palette: u8 = 0x00; 721 | 722 | if self.mask.show_background { 723 | if self.mask.show_background_left || (self.cycle >= 9) { 724 | let bit_mux: u16 = 0x8000 >> self.fine_x; 725 | 726 | let p0_pixel: u8 = ((self.bg_state.shifter_pattern_lo & bit_mux) > 0) as u8; 727 | let p1_pixel: u8 = ((self.bg_state.shifter_pattern_hi & bit_mux) > 0) as u8; 728 | 729 | bg_pixel = (p1_pixel << 1) | p0_pixel; 730 | 731 | let bg_pal0: u8 = ((self.bg_state.shifter_attrib_lo & bit_mux) > 0) as u8; 732 | let bg_pal1: u8 = ((self.bg_state.shifter_attrib_hi & bit_mux) > 0) as u8; 733 | bg_palette = (bg_pal1 << 1) | bg_pal0; 734 | } 735 | } 736 | 737 | // FG 738 | let mut fg_pixel: u8 = 0x00; 739 | let mut fg_palette: u8 = 0x00; 740 | let mut fg_priority: bool = false; 741 | 742 | if self.mask.show_sprites { 743 | if self.mask.show_sprites_left || self.cycle >= 9 { 744 | self.sprite_zero_being_rendered = false; 745 | 746 | for i in 0..self.sprite_state.count as usize { 747 | if self.sprite_state.scanline[i].x == 0 { 748 | // Determine the pixel value... 749 | let fg_pixel_lo: u8 = 750 | ((self.sprite_state.shifter_pattern_lo[i] & 0x80) > 0) as u8; 751 | let fg_pixel_hi: u8 = 752 | ((self.sprite_state.shifter_pattern_hi[i] & 0x80) > 0) as u8; 753 | fg_pixel = (fg_pixel_hi << 1) | fg_pixel_lo; 754 | 755 | fg_palette = (self.sprite_state.scanline[i].attr & 0x03) + 0x04; 756 | fg_priority = (self.sprite_state.scanline[i].attr & 0x20) == 0; 757 | 758 | if fg_pixel != 0 { 759 | if i == 0 { 760 | self.sprite_zero_being_rendered = true; 761 | } 762 | break; 763 | } 764 | } 765 | } 766 | } 767 | } 768 | 769 | // Compose/Order 770 | let mut pixel: u8 = 0x00; 771 | let mut palette: u8 = 0x00; 772 | 773 | if bg_pixel == 0 && fg_pixel == 0 { 774 | pixel = 0x00; 775 | palette = 0x00; 776 | } else if bg_pixel == 0 && fg_pixel > 0 { 777 | pixel = fg_pixel; 778 | palette = fg_palette; 779 | } else if bg_pixel > 0 && fg_pixel == 0 { 780 | pixel = bg_pixel; 781 | palette = bg_palette; 782 | } else if bg_pixel > 0 && fg_pixel > 0 { 783 | if fg_priority { 784 | pixel = fg_pixel; 785 | palette = fg_palette; 786 | } else { 787 | pixel = bg_pixel; 788 | palette = bg_palette; 789 | } 790 | 791 | if self.sprite_zero_hit_possible && self.sprite_zero_being_rendered { 792 | if self.mask.show_background & self.mask.show_sprites { 793 | if !(self.mask.show_background_left | self.mask.show_sprites_left) { 794 | if self.cycle >= 9 && self.cycle < 258 { 795 | self.status.sprite_zero_hit = true; 796 | } 797 | } else { 798 | if self.cycle >= 1 && self.cycle < 258 { 799 | self.status.sprite_zero_hit = true; 800 | } 801 | } 802 | } 803 | } 804 | } 805 | 806 | // Set pixel 807 | let c = self.get_color_from_pal(palette, pixel); 808 | if self.cycle > 0 && self.cycle <= 256 && self.scanline >= 0 && self.scanline < 240 { 809 | self.screen 810 | .set_pixel((self.cycle - 1) as usize, self.scanline as usize, c); 811 | } 812 | 813 | // advance cycles 814 | self.cycle += 1; 815 | 816 | if self.cycle >= 341 { 817 | self.cycle = 0; 818 | self.scanline += 1; 819 | if self.scanline >= 261 { 820 | self.scanline = -1; 821 | self.screen.complete = true; 822 | self.odd_frame = !self.odd_frame; 823 | } 824 | } 825 | } 826 | 827 | pub fn get_color_from_pal(&mut self, palette: u8, pixel: u8) -> u32 { 828 | return PALETTE[(self.ppu_read(0x3F00 + ((palette << 2) + pixel) as u16) & 0x3F) as usize]; 829 | } 830 | } 831 | -------------------------------------------------------------------------------- /src/nes/ram.rs: -------------------------------------------------------------------------------- 1 | use super::bus::CpuBusDevice; 2 | use std::ops::Range; 3 | 4 | const RAM_SIZE: u16 = 0x800; 5 | const RAM_RANGE: Range = 0x0000..0x1FFF; 6 | 7 | pub struct Ram { 8 | pub bytes: Vec, 9 | } 10 | 11 | impl CpuBusDevice for Ram { 12 | fn get_addr_range(&self) -> &Range { 13 | &RAM_RANGE 14 | } 15 | 16 | fn cpu_write(&mut self, addr: u16, data: u8) { 17 | self.bytes[(addr & (RAM_SIZE - 1)) as usize] = data; 18 | } 19 | 20 | fn cpu_read(&mut self, addr: u16) -> u8 { 21 | return self.bytes[(addr & (RAM_SIZE - 1)) as usize]; 22 | } 23 | } 24 | 25 | impl Ram { 26 | pub fn new() -> Self { 27 | Ram { 28 | bytes: vec![0; RAM_SIZE as usize], 29 | } 30 | } 31 | } 32 | --------------------------------------------------------------------------------