Compare commits
	
		
			13 Commits
		
	
	
		
			01064bab69
			...
			refactor
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9baa15050e | |||
| 4516ca8477 | |||
| 6087e3b20b | |||
| 10ac579c40 | |||
| ee5504111b | |||
| a628f64d28 | |||
| 318a6e0386 | |||
| db012c7f4b | |||
| e42c87aeb7 | |||
| 9113e95fa0 | |||
| 9973dc8714 | |||
| e128025208 | |||
| 44ac0c8ebd | 
							
								
								
									
										569
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										569
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,6 +3,7 @@ name = "gb" | |||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| authors = ["Rekai Musuka <rekai@musuka.dev>"] | authors = ["Rekai Musuka <rekai@musuka.dev>"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
|  | resolver = "2" | ||||||
|  |  | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  |  | ||||||
| @@ -11,7 +12,7 @@ anyhow = "^1.0" | |||||||
| bitfield = "^0.13" | bitfield = "^0.13" | ||||||
| clap = "^2.33" | clap = "^2.33" | ||||||
| gilrs = "^0.8" | gilrs = "^0.8" | ||||||
| pixels = "^0.5" | pixels = "^0.6" | ||||||
| winit = "^0.25" | winit = "^0.25" | ||||||
| winit_input_helper = "^0.10" | winit_input_helper = "^0.10" | ||||||
| rodio = "^0.14" | rodio = "^0.14" | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								src/apu.rs
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/apu.rs
									
									
									
									
									
								
							| @@ -468,22 +468,8 @@ impl Channel1 { | |||||||
|  |  | ||||||
|         // If this bit is set, a trigger event occurs |         // If this bit is set, a trigger event occurs | ||||||
|         if self.freq_hi.initial() { |         if self.freq_hi.initial() { | ||||||
|             // Envelope Behaviour during trigger event |             if self.is_dac_enabled() { | ||||||
|             self.period_timer = self.envelope.period(); |                 self.enabled = true; | ||||||
|             self.current_volume = self.envelope.init_vol(); |  | ||||||
|  |  | ||||||
|             // Sweep behaviour during trigger event |  | ||||||
|             let sweep_period = self.sweep.period(); |  | ||||||
|             let sweep_shift = self.sweep.shift_count(); |  | ||||||
|             self.shadow_freq = self.frequency(); |  | ||||||
|             self.sweep_timer = if sweep_period == 0 { 8 } else { sweep_period }; |  | ||||||
|  |  | ||||||
|             if sweep_period != 0 || sweep_shift != 0 { |  | ||||||
|                 self.sweep_enabled = true; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if sweep_shift != 0 { |  | ||||||
|                 let _ = self.calc_sweep_freq(); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // Length behaviour during trigger event |             // Length behaviour during trigger event | ||||||
| @@ -491,8 +477,21 @@ impl Channel1 { | |||||||
|                 self.length_timer = 64; |                 self.length_timer = 64; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if self.is_dac_enabled() { |             // Envelope Behaviour during trigger event | ||||||
|                 self.enabled = true; |             self.period_timer = self.envelope.period(); | ||||||
|  |             self.current_volume = self.envelope.init_vol(); | ||||||
|  |  | ||||||
|  |             // Sweep behaviour during trigger event | ||||||
|  |             let sweep_period = self.sweep.period(); | ||||||
|  |             let sweep_shift = self.sweep.shift_count(); | ||||||
|  |  | ||||||
|  |             self.shadow_freq = self.frequency(); | ||||||
|  |             self.sweep_timer = if sweep_period == 0 { 8 } else { sweep_period }; | ||||||
|  |  | ||||||
|  |             self.sweep_enabled = sweep_period != 0 || sweep_shift != 0; | ||||||
|  |  | ||||||
|  |             if sweep_shift != 0 { | ||||||
|  |                 let _ = self.calc_sweep_freq(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								src/bus.rs
									
									
									
									
									
								
							| @@ -7,14 +7,13 @@ use crate::ppu::{Ppu, PpuMode}; | |||||||
| use crate::serial::Serial; | use crate::serial::Serial; | ||||||
| use crate::timer::Timer; | use crate::timer::Timer; | ||||||
| use crate::work_ram::{VariableWorkRam, WorkRam}; | use crate::work_ram::{VariableWorkRam, WorkRam}; | ||||||
| use std::{fs::File, io::Read}; |  | ||||||
|  |  | ||||||
| const BOOT_ROM_SIZE: usize = 0x100; | pub(crate) const BOOT_SIZE: usize = 0x100; | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct Bus { | pub struct Bus { | ||||||
|     boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long |     boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long | ||||||
|     cartridge: Option<Cartridge>, |     cart: Option<Cartridge>, | ||||||
|     pub(crate) ppu: Ppu, |     pub(crate) ppu: Ppu, | ||||||
|     work_ram: WorkRam, |     work_ram: WorkRam, | ||||||
|     var_ram: VariableWorkRam, |     var_ram: VariableWorkRam, | ||||||
| @@ -30,7 +29,7 @@ impl Default for Bus { | |||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             boot: None, |             boot: None, | ||||||
|             cartridge: None, |             cart: None, | ||||||
|             ppu: Default::default(), |             ppu: Default::default(), | ||||||
|             work_ram: Default::default(), |             work_ram: Default::default(), | ||||||
|             var_ram: Default::default(), |             var_ram: Default::default(), | ||||||
| @@ -45,25 +44,19 @@ impl Default for Bus { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Bus { | impl Bus { | ||||||
|     pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> { |     pub(crate) fn with_boot(rom: [u8; 256]) -> Self { | ||||||
|         let mut file = File::open(path)?; |         Self { | ||||||
|         let mut boot_rom = [0u8; 256]; |             boot: Some(rom), | ||||||
|  |  | ||||||
|         file.read_exact(&mut boot_rom)?; |  | ||||||
|  |  | ||||||
|         Ok(Self { |  | ||||||
|             boot: Some(boot_rom), |  | ||||||
|             ..Default::default() |             ..Default::default() | ||||||
|         }) |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { |     pub(crate) fn load_cart(&mut self, rom: Vec<u8>) { | ||||||
|         self.cartridge = Some(Cartridge::new(path)?); |         self.cart = Some(Cartridge::new(rom)); | ||||||
|         Ok(()) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn rom_title(&self) -> Option<&str> { |     pub(crate) fn cart_title(&self) -> Option<&str> { | ||||||
|         self.cartridge.as_ref()?.title() |         self.cart.as_ref()?.title() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[allow(dead_code)] |     #[allow(dead_code)] | ||||||
| @@ -104,13 +97,13 @@ impl Bus { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 match self.cartridge.as_ref() { |                 match self.cart.as_ref() { | ||||||
|                     Some(cart) => cart.read_byte(addr), |                     Some(cart) => cart.read_byte(addr), | ||||||
|                     None => panic!("Tried to read from a non-existent cartridge"), |                     None => panic!("Tried to read from a non-existent cartridge"), | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM |             0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM | ||||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { |             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||||
|                 // 8KB External RAM |                 // 8KB External RAM | ||||||
|                 Some(cart) => cart.read_byte(addr), |                 Some(cart) => cart.read_byte(addr), | ||||||
|                 None => panic!("Tried to read from a non-existent cartridge"), |                 None => panic!("Tried to read from a non-existent cartridge"), | ||||||
| @@ -157,7 +150,7 @@ impl BusIo for Bus { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 match self.cartridge.as_ref() { |                 match self.cart.as_ref() { | ||||||
|                     Some(cart) => cart.read_byte(addr), |                     Some(cart) => cart.read_byte(addr), | ||||||
|                     None => panic!("Tried to read from a non-existent cartridge"), |                     None => panic!("Tried to read from a non-existent cartridge"), | ||||||
|                 } |                 } | ||||||
| @@ -169,7 +162,7 @@ impl BusIo for Bus { | |||||||
|                     _ => self.ppu.read_byte(addr), |                     _ => self.ppu.read_byte(addr), | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { |             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||||
|                 // 8KB External RAM |                 // 8KB External RAM | ||||||
|                 Some(cart) => cart.read_byte(addr), |                 Some(cart) => cart.read_byte(addr), | ||||||
|                 None => panic!("Tried to read from a non-existent cartridge"), |                 None => panic!("Tried to read from a non-existent cartridge"), | ||||||
| @@ -262,7 +255,7 @@ impl BusIo for Bus { | |||||||
|             0x0000..=0x7FFF => { |             0x0000..=0x7FFF => { | ||||||
|                 // 16KB ROM bank 00 (ends at 0x3FFF) |                 // 16KB ROM bank 00 (ends at 0x3FFF) | ||||||
|                 // and 16KB ROM Bank 01 -> NN (switchable via MB) |                 // and 16KB ROM Bank 01 -> NN (switchable via MB) | ||||||
|                 match self.cartridge.as_mut() { |                 match self.cart.as_mut() { | ||||||
|                     Some(cart) => cart.write_byte(addr, byte), |                     Some(cart) => cart.write_byte(addr, byte), | ||||||
|                     None => panic!("Tried to write into non-existent cartridge"), |                     None => panic!("Tried to write into non-existent cartridge"), | ||||||
|                 } |                 } | ||||||
| @@ -276,7 +269,7 @@ impl BusIo for Bus { | |||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => { |             0xA000..=0xBFFF => { | ||||||
|                 // 8KB External RAM |                 // 8KB External RAM | ||||||
|                 match self.cartridge.as_mut() { |                 match self.cart.as_mut() { | ||||||
|                     Some(cart) => cart.write_byte(addr, byte), |                     Some(cart) => cart.write_byte(addr, byte), | ||||||
|                     None => panic!("Tried to write into non-existent cartridge"), |                     None => panic!("Tried to write into non-existent cartridge"), | ||||||
|                 } |                 } | ||||||
|   | |||||||
							
								
								
									
										139
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										139
									
								
								src/cartridge.rs
									
									
									
									
									
								
							| @@ -1,7 +1,3 @@ | |||||||
| use std::fs::File; |  | ||||||
| use std::io::{self, Read}; |  | ||||||
| use std::path::Path; |  | ||||||
|  |  | ||||||
| use crate::bus::BusIo; | use crate::bus::BusIo; | ||||||
|  |  | ||||||
| const RAM_SIZE_ADDRESS: usize = 0x0149; | const RAM_SIZE_ADDRESS: usize = 0x0149; | ||||||
| @@ -17,19 +13,15 @@ pub(crate) struct Cartridge { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Cartridge { | impl Cartridge { | ||||||
|     pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> { |     pub(crate) fn new(memory: Vec<u8>) -> Self { | ||||||
|         let mut memory = vec![]; |  | ||||||
|         let mut rom = File::open(path)?; |  | ||||||
|         rom.read_to_end(&mut memory)?; |  | ||||||
|  |  | ||||||
|         let title = Self::find_title(&memory); |         let title = Self::find_title(&memory); | ||||||
|         eprintln!("Cartridge Title: {:?}", title); |         eprintln!("Cartridge Title: {:?}", title); | ||||||
|  |  | ||||||
|         Ok(Self { |         Self { | ||||||
|             mbc: Self::detect_mbc(&memory), |             mbc: Self::detect_mbc(&memory), | ||||||
|             title, |             title, | ||||||
|             memory, |             memory, | ||||||
|         }) |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> { |     fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> { | ||||||
| @@ -47,6 +39,8 @@ impl Cartridge { | |||||||
|             MBCKind::None => Box::new(NoMBC), |             MBCKind::None => Box::new(NoMBC), | ||||||
|             MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)), |             MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)), | ||||||
|             MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving |             MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving | ||||||
|  |             MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)), | ||||||
|  |             MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving | ||||||
|             MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)), |             MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)), | ||||||
|             MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving |             MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving | ||||||
|             MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)), |             MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)), | ||||||
| @@ -80,14 +74,18 @@ impl Cartridge { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn find_mbc(memory: &[u8]) -> MBCKind { |     fn find_mbc(memory: &[u8]) -> MBCKind { | ||||||
|  |         use MBCKind::*; | ||||||
|  |  | ||||||
|         match memory[MBC_TYPE_ADDRESS] { |         match memory[MBC_TYPE_ADDRESS] { | ||||||
|             0x00 => MBCKind::None, |             0x00 => None, | ||||||
|             0x01 | 0x02 => MBCKind::MBC1, |             0x01 | 0x02 => MBC1, | ||||||
|             0x03 => MBCKind::MBC1WithBattery, |             0x03 => MBC1WithBattery, | ||||||
|             0x19 | 0x1A => MBCKind::MBC5, |             0x05 => MBC2, | ||||||
|             0x1B => MBCKind::MBC5WithBattery, |             0x06 => MBC2WithBattery, | ||||||
|             0x13 => MBCKind::MBC3WithBattery, |             0x19 | 0x1A => MBC5, | ||||||
|             0x11 | 0x12 => MBCKind::MBC3, |             0x1B => MBC5WithBattery, | ||||||
|  |             0x13 => MBC3WithBattery, | ||||||
|  |             0x11 | 0x12 => MBC3, | ||||||
|             id => unimplemented!("id {:#04X} is an unsupported MBC", id), |             id => unimplemented!("id {:#04X} is an unsupported MBC", id), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -182,16 +180,16 @@ impl MBC1 { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn ram_addr(&self, addr: u16) -> u16 { |     fn ram_addr(&self, addr: u16) -> usize { | ||||||
|         use RamSize::*; |         use RamSize::*; | ||||||
|  |  | ||||||
|         match self.ram_size { |         match self.ram_size { | ||||||
|             Unused | One => (addr - 0xA000) % self.ram_size.capacity() as u16, |             Unused | One => (addr as usize - 0xA000) % self.ram_size.capacity(), | ||||||
|             Four => { |             Four => { | ||||||
|                 if self.mode { |                 if self.mode { | ||||||
|                     0x2000 * self.ram_bank as u16 + (addr - 0xA000) |                     0x2000 * self.ram_bank as usize + (addr as usize - 0xA000) | ||||||
|                 } else { |                 } else { | ||||||
|                     addr - 0xA000 |                     addr as usize - 0xA000 | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             _ => unreachable!("RAM size can not be greater than 32KB on MBC1"), |             _ => unreachable!("RAM size can not be greater than 32KB on MBC1"), | ||||||
| @@ -204,23 +202,17 @@ impl MBCIo for MBC1 { | |||||||
|         use MBCResult::*; |         use MBCResult::*; | ||||||
|  |  | ||||||
|         match addr { |         match addr { | ||||||
|             0x0000..=0x3FFF => { |             0x0000..=0x3FFF if self.mode => { | ||||||
|                 if self.mode { |                 Address(0x4000 * self.zero_bank() as usize + addr as usize) | ||||||
|                     Address(0x4000 * self.zero_bank() as usize + addr as usize) |  | ||||||
|                 } else { |  | ||||||
|                     Address(addr as usize) |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |             0x0000..=0x3FFF => Address(addr as usize), | ||||||
|             0x4000..=0x7FFF => { |             0x4000..=0x7FFF => { | ||||||
|                 Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)) |                 Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)) | ||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => { |             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||||
|                 if self.mem_enabled { |                 Value(self.memory[self.ram_addr(addr)]) | ||||||
|                     Value(self.memory[self.ram_addr(addr) as usize]) |  | ||||||
|                 } else { |  | ||||||
|                     Value(0xFF) |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|  |             0xA000..=0xBFFF => Value(0xFF), | ||||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr), |             _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -229,18 +221,14 @@ impl MBCIo for MBC1 { | |||||||
|         match addr { |         match addr { | ||||||
|             0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A, |             0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A, | ||||||
|             0x2000..=0x3FFF => { |             0x2000..=0x3FFF => { | ||||||
|                 self.rom_bank = if byte == 0x00 { |                 let value = byte & 0x1F; | ||||||
|                     0x01 |                 let masked_value = byte & self.rom_size_mask(); | ||||||
|                 } else { |                 self.rom_bank = if value == 0 { 0x01 } else { masked_value }; | ||||||
|                     byte & self.rom_size_mask() |  | ||||||
|                 }; |  | ||||||
|  |  | ||||||
|                 self.rom_bank &= 0x1F; |  | ||||||
|             } |             } | ||||||
|             0x4000..=0x5FFF => self.ram_bank = byte & 0x03, |             0x4000..=0x5FFF => self.ram_bank = byte & 0x03, | ||||||
|             0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, |             0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, | ||||||
|             0xA000..=0xBFFF if self.mem_enabled => { |             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||||
|                 let ram_addr = self.ram_addr(addr) as usize; |                 let ram_addr = self.ram_addr(addr); | ||||||
|                 self.memory[ram_addr] = byte; |                 self.memory[ram_addr] = byte; | ||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write |             0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write | ||||||
| @@ -405,6 +393,67 @@ impl MBCIo for MBC5 { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug)] | ||||||
|  | struct MBC2 { | ||||||
|  |     /// 4-bit number | ||||||
|  |     rom_bank: u8, | ||||||
|  |     memory: Box<[u8; Self::RAM_SIZE]>, | ||||||
|  |     mem_enabled: bool, | ||||||
|  |  | ||||||
|  |     rom_cap: usize, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl MBC2 { | ||||||
|  |     const RAM_SIZE: usize = 0x0200; | ||||||
|  |  | ||||||
|  |     fn new(rom_cap: usize) -> Self { | ||||||
|  |         Self { | ||||||
|  |             rom_bank: 0x01, | ||||||
|  |             memory: Box::new([0; Self::RAM_SIZE]), | ||||||
|  |             mem_enabled: Default::default(), | ||||||
|  |             rom_cap, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn rom_addr(&self, addr: u16) -> usize { | ||||||
|  |         (0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl MBCIo for MBC2 { | ||||||
|  |     fn handle_read(&self, addr: u16) -> MBCResult { | ||||||
|  |         use MBCResult::*; | ||||||
|  |  | ||||||
|  |         match addr { | ||||||
|  |             0x0000..=0x3FFF => Address(addr as usize), | ||||||
|  |             0x4000..=0x7FFF => Address(self.rom_addr(addr)), | ||||||
|  |             0xA000..=0xBFFF if self.mem_enabled => { | ||||||
|  |                 let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); | ||||||
|  |                 Value(self.memory[mbc2_addr] | 0xF0) | ||||||
|  |             } | ||||||
|  |             0xA000..=0xBFFF => Value(0xFF), | ||||||
|  |             _ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||||
|  |         let nybble = byte & 0x0F; | ||||||
|  |  | ||||||
|  |         match addr { | ||||||
|  |             0x0000..=0x3FFF if addr >> 8 & 0x01 == 0x01 => { | ||||||
|  |                 self.rom_bank = if nybble == 0x00 { 0x01 } else { nybble }; | ||||||
|  |             } | ||||||
|  |             0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A, | ||||||
|  |             0xA000..=0xBFFF if self.mem_enabled => { | ||||||
|  |                 let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); | ||||||
|  |                 self.memory[mbc2_addr] = nybble; | ||||||
|  |             } | ||||||
|  |             0x4000..=0x7FFF | 0xA000..=0xBFFF => {} | ||||||
|  |             _ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| struct NoMBC; | struct NoMBC; | ||||||
|  |  | ||||||
| @@ -434,6 +483,8 @@ enum MBCKind { | |||||||
|     None, |     None, | ||||||
|     MBC1, |     MBC1, | ||||||
|     MBC1WithBattery, |     MBC1WithBattery, | ||||||
|  |     MBC2, | ||||||
|  |     MBC2WithBattery, | ||||||
|     MBC3, |     MBC3, | ||||||
|     MBC3WithBattery, |     MBC3WithBattery, | ||||||
|     MBC5, |     MBC5, | ||||||
| @@ -446,7 +497,7 @@ impl Default for MBCKind { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy, PartialEq)] | ||||||
| enum RamSize { | enum RamSize { | ||||||
|     None = 0x00, |     None = 0x00, | ||||||
|     Unused = 0x01, |     Unused = 0x01, | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -1,10 +1,7 @@ | |||||||
| use crate::apu::Apu; | use crate::bus::{Bus, BusIo, BOOT_SIZE}; | ||||||
| use crate::bus::{Bus, BusIo}; |  | ||||||
| use crate::instruction::cycle::Cycle; |  | ||||||
| use crate::instruction::Instruction; | use crate::instruction::Instruction; | ||||||
| use crate::interrupt::{InterruptEnable, InterruptFlag}; | use crate::interrupt::{InterruptEnable, InterruptFlag}; | ||||||
| use crate::joypad::Joypad; | use crate::Cycle; | ||||||
| use crate::ppu::Ppu; |  | ||||||
| use bitfield::bitfield; | use bitfield::bitfield; | ||||||
| use std::fmt::{Display, Formatter, Result as FmtResult}; | use std::fmt::{Display, Formatter, Result as FmtResult}; | ||||||
|  |  | ||||||
| @@ -18,7 +15,7 @@ pub struct Cpu { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     pub fn new() -> Self { |     pub(crate) fn without_boot() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             reg: Registers { |             reg: Registers { | ||||||
|                 a: 0x01, |                 a: 0x01, | ||||||
| @@ -36,11 +33,11 @@ impl Cpu { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn boot_new(path: &str) -> anyhow::Result<Self> { |     pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self { | ||||||
|         Ok(Self { |         Self { | ||||||
|             bus: Bus::with_boot(path)?, |             bus: Bus::with_boot(rom), | ||||||
|             ..Default::default() |             ..Default::default() | ||||||
|         }) |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn ime(&self) -> ImeState { |     pub(crate) fn ime(&self) -> ImeState { | ||||||
| @@ -72,14 +69,6 @@ impl Cpu { | |||||||
|             _ => None, |             _ => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { |  | ||||||
|         self.bus.load_cartridge(path) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn rom_title(&self) -> Option<&str> { |  | ||||||
|         self.bus.rom_title() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
| @@ -133,7 +122,7 @@ impl Cpu { | |||||||
|             self.bus.clock(); |             self.bus.clock(); | ||||||
|  |  | ||||||
|             let elapsed = match kind { |             let elapsed = match kind { | ||||||
|                 ImeEnabled | NonePending => Cycle::new(4), |                 ImeEnabled | NonePending => 4, | ||||||
|                 SomePending => todo!("Implement HALT bug"), |                 SomePending => todo!("Implement HALT bug"), | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
| @@ -146,11 +135,11 @@ impl Cpu { | |||||||
|         self.handle_ei(); |         self.handle_ei(); | ||||||
|  |  | ||||||
|         // For use in Blargg's Test ROMs |         // For use in Blargg's Test ROMs | ||||||
|         // if self.read_byte(0xFF02) == 0x81 { |         if self.read_byte(0xFF02) == 0x81 { | ||||||
|         //     let c = self.read_byte(0xFF01) as char; |             let c = self.read_byte(0xFF01) as char; | ||||||
|         //     self.write_byte(0xFF02, 0x00); |             self.write_byte(0xFF02, 0x00); | ||||||
|         //     eprint!("{}", c); |             eprint!("{}", c); | ||||||
|         // } |         } | ||||||
|  |  | ||||||
|         elapsed |         elapsed | ||||||
|     } |     } | ||||||
| @@ -167,16 +156,12 @@ impl BusIo for Cpu { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     pub fn ppu(&mut self) -> &Ppu { |     pub(crate) fn bus(&self) -> &Bus { | ||||||
|         &self.bus.ppu |         &self.bus | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn apu_mut(&mut self) -> &mut Apu { |     pub(crate) fn bus_mut(&mut self) -> &mut Bus { | ||||||
|         &mut self.bus.apu |         &mut self.bus | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn joypad_mut(&mut self) -> &mut Joypad { |  | ||||||
|         &mut self.bus.joypad |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn handle_ei(&mut self) { |     fn handle_ei(&mut self) { | ||||||
|   | |||||||
							
								
								
									
										158
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								src/emu.rs
									
									
									
									
									
								
							| @@ -1,73 +1,121 @@ | |||||||
| use crate::cpu::Cpu as SM83; | use crate::apu::gen::SampleProducer; | ||||||
| use crate::instruction::cycle::Cycle; | use crate::cpu::Cpu; | ||||||
| use crate::joypad; | use crate::joypad::{self, Joypad}; | ||||||
| use crate::ppu::Ppu; | use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; | ||||||
| use anyhow::Result; |  | ||||||
| use gilrs::Gilrs; | use gilrs::Gilrs; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
| use winit_input_helper::WinitInputHelper; | use winit_input_helper::WinitInputHelper; | ||||||
|  |  | ||||||
| pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED); | pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED); | ||||||
| pub const CYCLES_IN_FRAME: Cycle = Cycle::new(456 * 154); // 456 Cycles times 154 scanlines | pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines | ||||||
| pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz | pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz | ||||||
| const DEFAULT_TITLE: &str = "DMG-01 Emulator"; | const DEFAULT_TITLE: &str = "DMG-01 Emulator"; | ||||||
| const GAMEPAD_ENABLED: bool = true; |  | ||||||
|  |  | ||||||
| pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> { | pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle { | ||||||
|     let mut cpu = match boot_path { |     let mut elapsed = 0; | ||||||
|         Some(path) => SM83::boot_new(path)?, |  | ||||||
|         None => SM83::new(), |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     eprintln!("Initialized GB Emulator"); |     if let Some(event) = gamepad.next_event() { | ||||||
|  |         joypad::handle_gamepad_input(emu.joyp_mut(), event); | ||||||
|     cpu.load_cartridge(rom_path)?; |  | ||||||
|     Ok(cpu) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn rom_title(game_boy: &SM83) -> &str { |  | ||||||
|     game_boy.rom_title().unwrap_or(DEFAULT_TITLE) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn run( |  | ||||||
|     game_boy: &mut SM83, |  | ||||||
|     gamepad: &mut Gilrs, |  | ||||||
|     input: &WinitInputHelper, |  | ||||||
|     target: Cycle, |  | ||||||
| ) -> Cycle { |  | ||||||
|     let mut elapsed = Cycle::new(0); |  | ||||||
|  |  | ||||||
|     if GAMEPAD_ENABLED { |  | ||||||
|         if let Some(event) = gamepad.next_event() { |  | ||||||
|             joypad::handle_gamepad_input(game_boy.joypad_mut(), event); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     joypad::handle_keyboard_input(game_boy.joypad_mut(), input); |     joypad::handle_keyboard_input(emu.joyp_mut(), key); | ||||||
|     while elapsed < target { |  | ||||||
|         elapsed += game_boy.step(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     elapsed |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn run_frame(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle { |  | ||||||
|     let mut elapsed = Cycle::new(0); |  | ||||||
|  |  | ||||||
|     if GAMEPAD_ENABLED { |  | ||||||
|         if let Some(event) = gamepad.next_event() { |  | ||||||
|             joypad::handle_gamepad_input(game_boy.joypad_mut(), event); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     joypad::handle_keyboard_input(game_boy.joypad_mut(), input); |  | ||||||
|     while elapsed < CYCLES_IN_FRAME { |     while elapsed < CYCLES_IN_FRAME { | ||||||
|         elapsed += game_boy.step(); |         elapsed += emu.step(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     elapsed |     elapsed | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn draw(ppu: &Ppu, frame: &mut [u8]) { | pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) { | ||||||
|     ppu.copy_to_gui(frame); |     buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf()); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct Emulator { | ||||||
|  |     cpu: Cpu, | ||||||
|  |     timestamp: Cycle, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Emulator { | ||||||
|  |     fn new(cpu: Cpu) -> Self { | ||||||
|  |         Self { | ||||||
|  |             cpu, | ||||||
|  |             timestamp: Default::default(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn step(&mut self) -> Cycle { | ||||||
|  |         self.cpu.step() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn load_cart(&mut self, rom: Vec<u8>) { | ||||||
|  |         self.cpu.bus_mut().load_cart(rom) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn joyp_mut(&mut self) -> &mut Joypad { | ||||||
|  |         &mut self.cpu.bus_mut().joypad | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn set_prod(&mut self, prod: SampleProducer<f32>) { | ||||||
|  |         self.cpu.bus_mut().apu.attach_producer(prod) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn title(&self) -> &str { | ||||||
|  |         self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub mod build { | ||||||
|  |     use std::fs::File; | ||||||
|  |     use std::io::{Read, Result}; | ||||||
|  |     use std::path::Path; | ||||||
|  |  | ||||||
|  |     use crate::bus::BOOT_SIZE; | ||||||
|  |     use crate::cpu::Cpu; | ||||||
|  |  | ||||||
|  |     use super::Emulator; | ||||||
|  |  | ||||||
|  |     #[derive(Debug, Default)] | ||||||
|  |     pub struct EmulatorBuilder { | ||||||
|  |         boot: Option<[u8; BOOT_SIZE]>, | ||||||
|  |         cart: Option<Vec<u8>>, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl EmulatorBuilder { | ||||||
|  |         pub fn new() -> Self { | ||||||
|  |             Default::default() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn with_boot<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||||
|  |             let mut file = File::open(path.as_ref())?; | ||||||
|  |  | ||||||
|  |             let mut buf = [0x00; BOOT_SIZE]; | ||||||
|  |             file.read_exact(&mut buf)?; | ||||||
|  |  | ||||||
|  |             self.boot = Some(buf); | ||||||
|  |             Ok(self) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn with_cart<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||||
|  |             let mut file = File::open(path.as_ref())?; | ||||||
|  |  | ||||||
|  |             let mut buf = Vec::new(); | ||||||
|  |             file.read_to_end(&mut buf)?; | ||||||
|  |  | ||||||
|  |             self.cart = Some(buf); | ||||||
|  |             Ok(self) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub fn finish(mut self) -> Emulator { | ||||||
|  |             let mut emu = Emulator::new(match self.boot { | ||||||
|  |                 Some(rom) => Cpu::with_boot(rom), | ||||||
|  |                 None => Cpu::without_boot(), | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             if let Some(rom) = self.cart.take() { | ||||||
|  |                 emu.load_cart(rom) | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             emu | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| pub use apu::gen::AudioSPSC; | pub use apu::gen::AudioSPSC; | ||||||
| pub use instruction::cycle::Cycle; | pub type Cycle = u64; | ||||||
|  |  | ||||||
| pub const GB_WIDTH: usize = 160; | pub const GB_WIDTH: usize = 160; | ||||||
| pub const GB_HEIGHT: usize = 144; | pub const GB_HEIGHT: usize = 144; | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,18 +1,20 @@ | |||||||
|  | use std::convert::TryInto; | ||||||
|  |  | ||||||
| use anyhow::{anyhow, Result}; | use anyhow::{anyhow, Result}; | ||||||
| use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; | use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; | ||||||
|  | use gb::emu::build::EmulatorBuilder; | ||||||
| use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH}; | use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH}; | ||||||
| use gilrs::Gilrs; | use gilrs::Gilrs; | ||||||
| use pixels::{PixelsBuilder, SurfaceTexture}; | use pixels::{PixelsBuilder, SurfaceTexture}; | ||||||
| use rodio::{OutputStream, Sink}; | use rodio::{OutputStream, Sink}; | ||||||
| use std::time::{Duration, Instant}; | use winit::dpi::{LogicalSize, PhysicalSize}; | ||||||
| use winit::dpi::LogicalSize; |  | ||||||
| use winit::event::{Event, VirtualKeyCode}; | use winit::event::{Event, VirtualKeyCode}; | ||||||
| use winit::event_loop::{ControlFlow, EventLoop}; | use winit::event_loop::{ControlFlow, EventLoop}; | ||||||
| use winit::window::{Window, WindowBuilder}; | use winit::window::{Window, WindowBuilder}; | ||||||
| use winit_input_helper::WinitInputHelper; | use winit_input_helper::WinitInputHelper; | ||||||
|  |  | ||||||
| const WINDOW_SCALE: f64 = 2.0; | const WINDOW_SCALE: usize = 3; | ||||||
| const AUDIO_ENABLED: bool = true; | const AUDIO_ENABLED: bool = false; | ||||||
|  |  | ||||||
| fn main() -> Result<()> { | fn main() -> Result<()> { | ||||||
|     let app = App::new(crate_name!()) |     let app = App::new(crate_name!()) | ||||||
| @@ -39,13 +41,15 @@ fn main() -> Result<()> { | |||||||
|         ) |         ) | ||||||
|         .get_matches(); |         .get_matches(); | ||||||
|  |  | ||||||
|     let rom_path = m |     let mut emu_build = | ||||||
|         .value_of("rom") |         EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?; | ||||||
|         .expect("Required value 'rom' was provided"); |  | ||||||
|  |  | ||||||
|     let mut game_boy = |     if let Some(path) = m.value_of("boot") { | ||||||
|         gb::emu::init(m.value_of("boot"), rom_path).expect("Initialize DMG-01 Emulator"); |         emu_build = emu_build.with_boot(path)?; | ||||||
|     let rom_title = gb::emu::rom_title(&game_boy); |     } | ||||||
|  |  | ||||||
|  |     let mut emu = emu_build.finish(); | ||||||
|  |     let rom_title = emu.title(); | ||||||
|  |  | ||||||
|     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); |     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); | ||||||
|  |  | ||||||
| @@ -69,17 +73,20 @@ fn main() -> Result<()> { | |||||||
|     if AUDIO_ENABLED { |     if AUDIO_ENABLED { | ||||||
|         let spsc: AudioSPSC<f32> = Default::default(); |         let spsc: AudioSPSC<f32> = Default::default(); | ||||||
|         let (prod, cons) = spsc.init(); |         let (prod, cons) = spsc.init(); | ||||||
|         let sink = Sink::try_new(&stream_handle)?; |         let sink = { | ||||||
|         sink.append(cons); |             let s = Sink::try_new(&stream_handle)?; | ||||||
|         game_boy.apu_mut().attach_producer(prod); |             s.append(cons); | ||||||
|  |             s.set_volume(0.1); | ||||||
|  |             s | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         emu.set_prod(prod); | ||||||
|  |  | ||||||
|         std::thread::spawn(move || { |         std::thread::spawn(move || { | ||||||
|             sink.sleep_until_end(); |             sink.sleep_until_end(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let mut start = Instant::now(); |  | ||||||
|     let frame_time = Duration::from_secs_f64(1.0 / 59.73); // 59.73 Hz on Host |  | ||||||
|     let mut cycle_count: Cycle = Default::default(); |     let mut cycle_count: Cycle = Default::default(); | ||||||
|  |  | ||||||
|     event_loop.run(move |event, _, control_flow| { |     event_loop.run(move |event, _, control_flow| { | ||||||
| @@ -104,57 +111,34 @@ fn main() -> Result<()> { | |||||||
|                 pixels.resize_surface(size.width, size.height); |                 pixels.resize_surface(size.width, size.height); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             let mut diff = Instant::now() - start; |             cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input); | ||||||
|             while diff.subsec_nanos() < frame_time.subsec_nanos() { |  | ||||||
|                 if cycle_count < gb::emu::CYCLES_IN_FRAME { |  | ||||||
|                     cycle_count += gb::emu::run_frame(&mut game_boy, &mut gamepad, &input); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 diff = Instant::now() - start; |  | ||||||
|             } |  | ||||||
|             start = Instant::now(); |  | ||||||
|  |  | ||||||
|             if cycle_count >= gb::emu::CYCLES_IN_FRAME { |             if cycle_count >= gb::emu::CYCLES_IN_FRAME { | ||||||
|                 cycle_count %= gb::emu::CYCLES_IN_FRAME; |                 cycle_count %= gb::emu::CYCLES_IN_FRAME; | ||||||
|  |  | ||||||
|                 gb::emu::draw(game_boy.ppu(), pixels.get_frame()); |                 let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels | ||||||
|  |                     .get_frame() | ||||||
|  |                     .try_into() | ||||||
|  |                     .expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4"); | ||||||
|  |  | ||||||
|  |                 gb::emu::draw_frame(&emu, buf); | ||||||
|                 window.request_redraw(); |                 window.request_redraw(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(not(windows))] |  | ||||||
| fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { | fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { | ||||||
|     let size = LogicalSize::new( |     let logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64); | ||||||
|         (GB_WIDTH as f64) * WINDOW_SCALE, |     let physical = PhysicalSize::new( | ||||||
|         (GB_HEIGHT as f64) * WINDOW_SCALE, |         (GB_WIDTH * WINDOW_SCALE) as f32, | ||||||
|  |         (GB_HEIGHT * WINDOW_SCALE) as f32, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     Ok(WindowBuilder::new() |     Ok(WindowBuilder::new() | ||||||
|         .with_title(title) |         .with_title(title) | ||||||
|         .with_inner_size(size) |         .with_min_inner_size(logical) | ||||||
|         .with_min_inner_size(size) |         .with_inner_size(physical) | ||||||
|         .with_resizable(true) |         .with_resizable(true) | ||||||
|         .with_decorations(true) |  | ||||||
|         .with_transparent(false) |  | ||||||
|         .build(event_loop)?) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(windows)] |  | ||||||
| fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { |  | ||||||
|     use winit::platform::windows::WindowBuilderExtWindows; |  | ||||||
|  |  | ||||||
|     let size = LogicalSize::new( |  | ||||||
|         (GB_WIDTH as f64) * WINDOW_SCALE, |  | ||||||
|         (GB_HEIGHT as f64) * WINDOW_SCALE, |  | ||||||
|     ); |  | ||||||
|     Ok(WindowBuilder::new() |  | ||||||
|         .with_title(title) |  | ||||||
|         .with_inner_size(size) |  | ||||||
|         .with_min_inner_size(size) |  | ||||||
|         .with_resizable(true) |  | ||||||
|         .with_decorations(true) |  | ||||||
|         .with_transparent(false) |  | ||||||
|         .with_drag_and_drop(false) |  | ||||||
|         .build(event_loop)?) |         .build(event_loop)?) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/ppu.rs
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| use crate::bus::BusIo; | use crate::bus::BusIo; | ||||||
| use crate::instruction::cycle::Cycle; | use crate::Cycle; | ||||||
| use crate::GB_HEIGHT; | use crate::GB_HEIGHT; | ||||||
| use crate::GB_WIDTH; | use crate::GB_WIDTH; | ||||||
| use dma::DirectMemoryAccess; | use dma::DirectMemoryAccess; | ||||||
| @@ -79,7 +79,7 @@ impl Ppu { | |||||||
|  |  | ||||||
|         match self.stat.mode() { |         match self.stat.mode() { | ||||||
|             PpuMode::OamScan => { |             PpuMode::OamScan => { | ||||||
|                 if self.cycle >= 80.into() { |                 if self.cycle >= 80 { | ||||||
|                     self.stat.set_mode(PpuMode::Drawing); |                     self.stat.set_mode(PpuMode::Drawing); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
| @@ -88,7 +88,7 @@ impl Ppu { | |||||||
|             PpuMode::Drawing => { |             PpuMode::Drawing => { | ||||||
|                 if self.ctrl.lcd_enabled() { |                 if self.ctrl.lcd_enabled() { | ||||||
|                     // Only Draw when the LCD Is Enabled |                     // Only Draw when the LCD Is Enabled | ||||||
|                     self.draw(self.cycle.into()); |                     self.draw(self.cycle); | ||||||
|                 } else { |                 } else { | ||||||
|                     self.reset(); |                     self.reset(); | ||||||
|                 } |                 } | ||||||
| @@ -125,7 +125,7 @@ impl Ppu { | |||||||
|             PpuMode::HBlank => { |             PpuMode::HBlank => { | ||||||
|                 // This mode will always end at 456 cycles |                 // This mode will always end at 456 cycles | ||||||
|  |  | ||||||
|                 if self.cycle >= 456.into() { |                 if self.cycle >= 456 { | ||||||
|                     self.cycle %= 456; |                     self.cycle %= 456; | ||||||
|                     self.pos.line_y += 1; |                     self.pos.line_y += 1; | ||||||
|  |  | ||||||
| @@ -167,7 +167,7 @@ impl Ppu { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             PpuMode::VBlank => { |             PpuMode::VBlank => { | ||||||
|                 if self.cycle > 456.into() { |                 if self.cycle > 456 { | ||||||
|                     self.cycle %= 456; |                     self.cycle %= 456; | ||||||
|                     self.pos.line_y += 1; |                     self.pos.line_y += 1; | ||||||
|  |  | ||||||
| @@ -228,7 +228,7 @@ impl Ppu { | |||||||
|         self.scan_state.next(); |         self.scan_state.next(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn draw(&mut self, _cycle: u32) { |     fn draw(&mut self, _cycle: Cycle) { | ||||||
|         use FetcherState::*; |         use FetcherState::*; | ||||||
|  |  | ||||||
|         let mut iter = self.obj_buffer.iter_mut(); |         let mut iter = self.obj_buffer.iter_mut(); | ||||||
| @@ -418,8 +418,8 @@ impl Ppu { | |||||||
|         self.frame_buf.swap_with_slice(&mut blank); |         self.frame_buf.swap_with_slice(&mut blank); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn copy_to_gui(&self, frame: &mut [u8]) { |     pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { | ||||||
|         frame.copy_from_slice(self.frame_buf.as_ref()); |         &self.frame_buf | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn clock_fifo(&mut self) -> Option<GrayShade> { |     fn clock_fifo(&mut self) -> Option<GrayShade> { | ||||||
| @@ -470,7 +470,7 @@ impl Default for Ppu { | |||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             vram: Box::new([0u8; VRAM_SIZE]), |             vram: Box::new([0u8; VRAM_SIZE]), | ||||||
|             cycle: Cycle::new(0), |             cycle: Default::default(), | ||||||
|             frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]), |             frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]), | ||||||
|             int: Default::default(), |             int: Default::default(), | ||||||
|             ctrl: Default::default(), |             ctrl: Default::default(), | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| use crate::instruction::cycle::Cycle; | use crate::Cycle; | ||||||
|  |  | ||||||
| #[derive(Debug, Default)] | #[derive(Debug, Default)] | ||||||
| pub(crate) struct DirectMemoryAccess { | pub(crate) struct DirectMemoryAccess { | ||||||
| @@ -56,7 +56,7 @@ impl DirectMemoryAccess { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn reset(&mut self) { |     fn reset(&mut self) { | ||||||
|         self.cycle = Cycle::new(0); |         self.cycle = 0; | ||||||
|         self.state = DmaState::Disabled; |         self.state = DmaState::Disabled; | ||||||
|         self.start.0 = None; |         self.start.0 = None; | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										79
									
								
								src/scheduler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/scheduler.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | use crate::Cycle; | ||||||
|  | use std::collections::BinaryHeap; | ||||||
|  |  | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub(crate) struct Scheduler { | ||||||
|  |     timestamp: Cycle, | ||||||
|  |     queue: BinaryHeap<Event>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Scheduler { | ||||||
|  |     pub(crate) fn init() -> Self { | ||||||
|  |         let mut scheduler = Self { | ||||||
|  |             timestamp: Default::default(), | ||||||
|  |             queue: Default::default(), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         scheduler.push(Event { | ||||||
|  |             kind: EventKind::TimestampOverflow, | ||||||
|  |             timestamp: Cycle::MAX, | ||||||
|  |             cb: |_delay| panic!("Reached Cycle::MAX"), | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         scheduler | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn push(&mut self, event: Event) { | ||||||
|  |         self.queue.push(event); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn step(&mut self, cycles: Cycle) { | ||||||
|  |         self.timestamp += cycles; | ||||||
|  |  | ||||||
|  |         loop { | ||||||
|  |             let should_pop = match self.queue.peek() { | ||||||
|  |                 Some(event) => self.timestamp >= event.timestamp, | ||||||
|  |                 None => false, | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             if !should_pop { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             let event = self.queue.pop().expect("Pop Event from Scheduler Queue"); | ||||||
|  |  | ||||||
|  |             (event.cb)(self.timestamp - event.timestamp); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub(crate) struct Event { | ||||||
|  |     kind: EventKind, | ||||||
|  |     cb: fn(Cycle), | ||||||
|  |     pub(crate) timestamp: Cycle, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Eq for Event {} | ||||||
|  | impl PartialEq for Event { | ||||||
|  |     fn eq(&self, other: &Self) -> bool { | ||||||
|  |         self.kind == other.kind && self.timestamp == other.timestamp | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl PartialOrd for Event { | ||||||
|  |     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | ||||||
|  |         Some(self.cmp(other)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Ord for Event { | ||||||
|  |     fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||||||
|  |         self.timestamp.cmp(&other.timestamp) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
|  | pub(crate) enum EventKind { | ||||||
|  |     TimestampOverflow, | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user