Compare commits

...

4 Commits

9 changed files with 114 additions and 37 deletions

1
Cargo.lock generated
View File

@ -1171,6 +1171,7 @@ dependencies = [
"log", "log",
"pixels", "pixels",
"rand", "rand",
"wgpu",
"winit", "winit",
"winit_input_helper", "winit_input_helper",
] ]

View File

@ -14,5 +14,6 @@ rand = "0.8"
env_logger = "0.9" env_logger = "0.9"
log = "0.4" log = "0.4"
pixels = "0.7" pixels = "0.7"
wgpu = "0.11"
winit = "0.25" winit = "0.25"
winit_input_helper = "0.10" winit_input_helper = "0.10"

View File

@ -57,26 +57,17 @@ pub struct Bus {
impl Bus { impl Bus {
pub fn new() -> Self { pub fn new() -> Self {
let game_rom = match ROM::load_file("/home/fran/Development/Personal/Rust/rmg-001/ignore/mario-land.gb".to_string()) { let args: Vec<String> = std::env::args().collect();
// let game_rom = match ROM::load_file("ignore/dmg-acid2.gb".to_string()) { if args.len() < 2 {
// let game_rom = match ROM::load_file("ignore/mario-land.gb".to_string()) { println!("Please, specify a ROM file");
// let game_rom = match ROM::load_file("ignore/tetris.gb".to_string()) { std::process::exit(1);
// let game_rom = match ROM::load_file("ignore/mooneye/emulator-only/mbc1/bits_bank1.gb".to_string()) { }
// let game_rom = match ROM::load_file("roms/cpu_instrs.gb".to_string()) { let game_rom = match ROM::load_file(&args[1]) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/01-special.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/02-interrupts.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/03-op sp,hl.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/04-op r,imm.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/05-op rp.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/06-ld r,r.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/07-jr,jp,call,ret,rst.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/08-misc instrs.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/09-op r,r.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/10-bit ops.gb".to_string()) {
// let game_rom = match ROM::load_file("roms/cpu_instrs_individual/11-op a,(hl).gb".to_string()) {
Ok(rom) => rom, Ok(rom) => rom,
// _ => ROM::from_bytes(&[0; 0xFFFF]) Err(err) => {
_ => panic!("Could not read ROM"), println!("Could not read ROM: {}", err);
std::process::exit(1);
},
}; };
let mut data = [0x00; 0x10000]; let mut data = [0x00; 0x10000];
// Hardware registers after the bootrom // Hardware registers after the bootrom
@ -147,9 +138,13 @@ impl Bus {
self.reset_timer = true; self.reset_timer = true;
} else if address == LCD_CONTROL_ADDRESS { } else if address == LCD_CONTROL_ADDRESS {
self.data[address as usize] = data; self.data[address as usize] = data;
// Check if LCD is being turned on // Check if LCD is being turned on or off
if get_bit(data, BitIndex::I7) && !get_bit(self.data[address as usize], BitIndex::I7) { if (get_bit(data, BitIndex::I7) && !get_bit(self.data[address as usize], BitIndex::I7)) ||
!get_bit(data, BitIndex::I7) {
self.data[LCD_Y_ADDRESS as usize] = 0x00; self.data[LCD_Y_ADDRESS as usize] = 0x00;
// Set Hblank
let byte = self.data[LCD_STATUS_ADDRESS as usize];
self.data[LCD_STATUS_ADDRESS as usize] = byte & 0b11111100;
} }
} else if address == LCD_Y_ADDRESS { } else if address == LCD_Y_ADDRESS {
// println!("Write to LCD_Y not allowed"); // println!("Write to LCD_Y not allowed");

View File

@ -857,7 +857,7 @@ impl CPU {
is_halted: false, is_halted: false,
ei_delay: false, ei_delay: false,
ime: true, ime: true,
enable_logs: !env::var("CPU_LOG").is_err(), enable_logs: !env::var("CPU_LOG").is_err() || !env::var("CPU_LOGS").is_err(),
} }
} }

37
src/frames.rs Normal file
View File

@ -0,0 +1,37 @@
use std::time::Instant;
pub struct Frames {
count: usize,
timer: Instant,
time_start: u128,
}
impl Frames {
pub fn new() -> Self {
Self {
count: 0,
timer: Instant::now(),
time_start: 0,
}
}
pub fn reset_count(&mut self) {
self.count = 0;
}
pub fn reset_timer(&mut self) {
self.time_start = self.timer.elapsed().as_millis();
}
pub fn increment(&mut self) {
self.count = self.count.saturating_add(1);
}
pub fn elapsed_ms(&self) -> u128 {
self.timer.elapsed().as_millis().saturating_sub(self.time_start)
}
pub fn count(&self) -> usize {
self.count
}
}

View File

@ -7,3 +7,4 @@ pub mod bus;
pub mod joypad; pub mod joypad;
pub mod emulator; pub mod emulator;
pub mod render; pub mod render;
pub mod frames;

View File

@ -23,8 +23,8 @@ pub const DMA_ADDRESS: u16 = 0xFF46;
pub const BACKGROUND_PALETTE_ADDRESS: u16 = 0xFF47; pub const BACKGROUND_PALETTE_ADDRESS: u16 = 0xFF47;
pub const OBJECT_PALETTE_0_ADDRESS: u16 = 0xFF48; pub const OBJECT_PALETTE_0_ADDRESS: u16 = 0xFF48;
pub const OBJECT_PALETTE_1_ADDRESS: u16 = 0xFF49; pub const OBJECT_PALETTE_1_ADDRESS: u16 = 0xFF49;
pub const WINDOW_X_ADDRESS: u16 = 0xFF4B;
pub const WINDOW_Y_ADDRESS: u16 = 0xFF4A; pub const WINDOW_Y_ADDRESS: u16 = 0xFF4A;
pub const WINDOW_X_ADDRESS: u16 = 0xFF4B;
pub const TILE_MAP_ADDRESS: u16 = 0x9800; pub const TILE_MAP_ADDRESS: u16 = 0x9800;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]

View File

@ -1,11 +1,12 @@
use crate::emulator::Emulator; use crate::emulator::Emulator;
use crate::frames::Frames;
use crate::cpu::{Cycles}; use crate::cpu::{Cycles};
use crate::ppu::{WIDTH, HEIGHT}; use crate::ppu::{WIDTH, HEIGHT};
use std::{thread, time}; use std::{thread, time};
use log::error; use log::error;
use pixels::{Pixels, SurfaceTexture}; use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
use winit::dpi::LogicalSize; use winit::dpi::LogicalSize;
use winit::event::{Event, VirtualKeyCode, WindowEvent}; use winit::event::{Event, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
@ -15,7 +16,20 @@ use winit_input_helper::WinitInputHelper;
pub fn create_pixels(width: u32, height: u32, window: &Window) -> Pixels { pub fn create_pixels(width: u32, height: u32, window: &Window) -> Pixels {
let window_size = window.inner_size(); let window_size = window.inner_size();
let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, window); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, window);
Pixels::new(width, height, surface_texture).unwrap() // Pixels::new(width, height, surface_texture).unwrap()
PixelsBuilder::new(width, height, surface_texture)
.device_descriptor(wgpu::DeviceDescriptor {
limits: wgpu::Limits {
max_storage_textures_per_shader_stage: 4,
max_texture_dimension_2d: 4096,
max_texture_dimension_1d: 4096,
..wgpu::Limits::default()
},
..wgpu::DeviceDescriptor::default()
})
.enable_vsync(false)
.build()
.unwrap()
} }
pub fn create_window<T>(width: u32, height: u32, title: String, event_loop: &EventLoop<T>) -> Window { pub fn create_window<T>(width: u32, height: u32, title: String, event_loop: &EventLoop<T>) -> Window {
@ -29,6 +43,9 @@ pub fn create_window<T>(width: u32, height: u32, title: String, event_loop: &Eve
} }
pub fn start_eventloop() { pub fn start_eventloop() {
let mut emulator = Emulator::new();
let mut frames = Frames::new();
env_logger::init(); env_logger::init();
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let mut input = WinitInputHelper::new(); let mut input = WinitInputHelper::new();
@ -36,8 +53,6 @@ pub fn start_eventloop() {
let window = create_window(WIDTH, HEIGHT, "rmg-001".to_string(), &event_loop); let window = create_window(WIDTH, HEIGHT, "rmg-001".to_string(), &event_loop);
let mut pixels = create_pixels(WIDTH, HEIGHT, &window); let mut pixels = create_pixels(WIDTH, HEIGHT, &window);
let mut emulator = Emulator::new();
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait; *control_flow = ControlFlow::Wait;
@ -67,6 +82,12 @@ pub fn start_eventloop() {
}, },
Event::MainEventsCleared => { Event::MainEventsCleared => {
emulator.run(Cycles(70224), pixels.get_frame()); emulator.run(Cycles(70224), pixels.get_frame());
frames.increment();
if frames.elapsed_ms() >= 1000 {
window.set_title(&format!("rmg-001 (FPS: {})", frames.count()));
frames.reset_count();
frames.reset_timer();
}
// thread::sleep(time::Duration::from_millis(1)); // thread::sleep(time::Duration::from_millis(1));
window.request_redraw(); window.request_redraw();

View File

@ -14,6 +14,7 @@ pub const RAM_SIZE_ADDRESS: u16 = 0x0149;
pub const ROM_SIZE_ADDRESS: u16 = 0x0148; pub const ROM_SIZE_ADDRESS: u16 = 0x0148;
pub const DESTINATION_CODE_ADDRESS: u16 = 0x014A; pub const DESTINATION_CODE_ADDRESS: u16 = 0x014A;
#[derive(Debug)]
enum Region { enum Region {
Japanese, Japanese,
NonJapanese, NonJapanese,
@ -37,11 +38,13 @@ enum MBC {
BandaiTIMA5, BandaiTIMA5,
} }
#[derive(Debug)]
enum BankingMode { enum BankingMode {
Simple, Simple,
Advanced, Advanced,
} }
#[derive(Debug)]
pub struct ROMInfo { pub struct ROMInfo {
mbc: MBC, mbc: MBC,
publisher: String, publisher: String,
@ -139,7 +142,7 @@ impl ROMInfo {
} }
pub fn ram_size(&self) -> usize { pub fn ram_size(&self) -> usize {
0x4000 * self.ram_banks as usize 0x2000 * self.ram_banks as usize
} }
} }
@ -154,14 +157,17 @@ pub struct ROM {
} }
impl ROM { impl ROM {
pub fn load_file(filename: String) -> std::io::Result<Self> { pub fn load_file(filename: &str) -> std::io::Result<Self> {
let mut file = File::open(filename)?; let mut file = File::open(filename)?;
let mut data = vec![]; let mut data = vec![];
file.read_to_end(&mut data)?; file.read_to_end(&mut data)?;
let info = ROMInfo::from_bytes(&data); let info = ROMInfo::from_bytes(&data);
println!("has ram {}", info.has_ram); println!("MBC {:?}", info.mbc);
println!("mbc {:?}", info.mbc); println!("Has RAM {}", info.has_ram);
println!("ROM banks {}", info.rom_banks);
println!("RAM banks {}", info.ram_banks);
println!("Region {:?}", info.region);
let ram = Vec::with_capacity(info.ram_size() as usize); let ram = Vec::with_capacity(info.ram_size() as usize);
Ok(Self { Ok(Self {
@ -177,6 +183,12 @@ impl ROM {
pub fn read(&self, address: u16) -> u8 { pub fn read(&self, address: u16) -> u8 {
match self.info.mbc { match self.info.mbc {
MBC::NoMBC => {
return match self.data.get(address as usize) {
Some(data) => *data,
None => 0xFF,
};
},
MBC::MBC1 => { MBC::MBC1 => {
if BANK_ZERO.in_range(address) { if BANK_ZERO.in_range(address) {
return self.data[address as usize]; return self.data[address as usize];
@ -188,19 +200,26 @@ impl ROM {
if !self.info.has_ram { if !self.info.has_ram {
return 0xFF; return 0xFF;
} }
return self.ram[(address - EXTERNAL_RAM.begin() + (EXTERNAL_RAM.begin() * self.ram_bank as u16)) as usize]; return match self.ram.get((address - EXTERNAL_RAM.begin() + (0x2000 * self.ram_bank as u16)) as usize) {
Some(data) => *data,
None => 0xFF,
};
} }
unreachable!("ROM read: Address {} not valid", address);
}, },
_ => {}, _ => unimplemented!(),
} }
self.data[address as usize] self.data[address as usize]
} }
pub fn write(&mut self, address: u16, data: u8) { pub fn write(&mut self, address: u16, data: u8) {
match self.info.mbc { match self.info.mbc {
MBC::NoMBC => {},
MBC::MBC1 => { MBC::MBC1 => {
if address >= 0x0000 && address <= 0x1FFF { // RAM enable register if address >= 0x0000 && address <= 0x1FFF { // RAM enable register
if !self.info.has_ram {
return;
}
self.ram_enable = match data & 0x0F { self.ram_enable = match data & 0x0F {
0x0A => true, 0x0A => true,
_ => false, _ => false,
@ -218,12 +237,14 @@ impl ROM {
if !self.ram_enable || !self.info.has_ram { if !self.ram_enable || !self.info.has_ram {
return; return;
} }
let address = (address - EXTERNAL_RAM.begin() + (EXTERNAL_RAM.begin() * self.ram_bank as u16)) as usize; let address = address as usize - EXTERNAL_RAM.begin() as usize + (EXTERNAL_RAM.begin() as usize * self.ram_bank as usize);
self.ram[address] = data; if let Some(elem) = self.ram.get_mut(address) {
*elem = data;
}
self.switch_rom_bank(self.rom_bank + (data as u16 >> 5)); self.switch_rom_bank(self.rom_bank + (data as u16 >> 5));
} }
}, },
_ => {}, _ => unimplemented!(),
} }
} }