Compare commits
	
		
			180 Commits
		
	
	
		
			192bdffd64
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8a352ca807 | |||
| 068e4124a6 | |||
| fedae65cc8 | |||
| 41586d8cc4 | |||
| 79740ac245 | |||
| d6bfde081e | |||
| d2f57ee66b | |||
| 3841d3a98c | |||
| 85940c8744 | |||
| c7adf54976 | |||
| f0f9003247 | |||
| 5eba097ad4 | |||
| 756c806c2a | |||
| 77af5453f0 | |||
| 41bc4f7afe | |||
| 36e46d3780 | |||
| 926c66746d | |||
| cf2357f917 | |||
| 3fb7edd333 | |||
| 3ab512d663 | |||
| 61156ca8a8 | |||
| 2c411d5792 | |||
| 4c657c9832 | |||
| e72b11f946 | |||
| bbcbbd8ce3 | |||
| c4226e6e76 | |||
| 409314a4e5 | |||
| 952bf68bd0 | |||
| 865c11d53c | |||
| 90d2da9272 | |||
| 4a1a21a08f | |||
| 9d23b571fb | |||
| 70e0b5868d | |||
| a0e3c7c602 | |||
| ef4f3d9ec6 | |||
| 7f872a554b | |||
| dda0257655 | |||
| c10816c048 | |||
| 2405fd027f | |||
| 939c25ce1a | |||
| 6f5e863645 | |||
| ce5d58e1d2 | |||
| ebcd5bec5c | |||
| f2c49b398c | |||
| 6f76571d6c | |||
| 55da5a29d8 | |||
| 7dfcd44820 | |||
| 80a15039e0 | |||
| 32405c0734 | |||
| 2ef8fefbb7 | |||
| 548945a4ee | |||
| 1526b4a09d | |||
| 4125ea5c74 | |||
| b9519d9b7a | |||
| 01278ca83f | |||
| 2bb8bd6d8f | |||
| 080c1e7518 | |||
| 765f9d8288 | |||
| 8780c4a59b | |||
| e19a540650 | |||
| 4cfd951ede | |||
| de8fe1a367 | |||
| c5fa41b20d | |||
| 552cfd4a18 | |||
| 4c516804e4 | |||
| 9b3ab73bb1 | |||
| d9f1d661ae | |||
| da83032e24 | |||
| 67e2447d3f | |||
| 37cf3d92e4 | |||
| 293e5762c3 | |||
| 999f661e6b | |||
| e573411890 | |||
| 352a65b705 | |||
| 64230973f1 | |||
| dbbf87af52 | |||
| 1440cd1fc7 | |||
| 9964b49ce1 | |||
| 142231d355 | |||
| 227928e8ca | |||
| 1001b0b124 | |||
| 71ce3f43e0 | |||
| ce121864d2 | |||
| e1fe00ab64 | |||
| 5882678bc5 | |||
| bcd67cb317 | |||
| 4516ca8477 | |||
| 6087e3b20b | |||
| 10ac579c40 | |||
| ee5504111b | |||
| a628f64d28 | |||
| 318a6e0386 | |||
| db012c7f4b | |||
| e42c87aeb7 | |||
| 9113e95fa0 | |||
| 9973dc8714 | |||
| e128025208 | |||
| 44ac0c8ebd | |||
| 01064bab69 | |||
| 634bc2d2c0 | |||
| d794a94b68 | |||
| b87e31d3f4 | |||
| 3c2456611e | |||
| b829f05a34 | |||
| afd2d16371 | |||
| 1f8fa48168 | |||
| c6fbb79189 | |||
| 8b78b2943e | |||
| 0af95a1dd3 | |||
| 9fa40f8584 | |||
| b10bc7b4fd | |||
| 4658a0d106 | |||
| f92b9d61ef | |||
| 360a9a7b65 | |||
| 5d64e539a7 | |||
| 22f96a10e7 | |||
| 8fea8eb1ff | |||
| 9b2c91445a | |||
| b9046bceba | |||
| aa22e93049 | |||
| 6215eccb2f | |||
| a77d0a0f62 | |||
| c2f2e2194b | |||
| d68257bb29 | |||
| e27d6dc25b | |||
| 1acb5de19d | |||
| 1b78b248a3 | |||
| 5d6df46a2d | |||
| 7e65d82fef | |||
| 8c9567b610 | |||
| 53dfaf0de2 | |||
| 16c2dd81fc | |||
| 79be38a1e6 | |||
| 8625bec059 | |||
| 0637b771e3 | |||
| 0107fa04c9 | |||
| 6265c8af04 | |||
| 5482a8e75f | |||
| 002dae6826 | |||
| c863dc835c | |||
| d4407cf849 | |||
| de0d147685 | |||
| dd8339e8de | |||
| 05d6475015 | |||
| 32b597a328 | |||
| 33be2e0e83 | |||
| 832e1b7633 | |||
| a549b9feef | |||
| 4d6fc95130 | |||
| 7112cd15e3 | |||
| 2af673d7c5 | |||
| e43cac8041 | |||
| 8724da824e | |||
| 903cfacad3 | |||
| 9d0e099a97 | |||
| d9a3a7b0dd | |||
| 1a4546f4a4 | |||
| bd685a99a4 | |||
| 31abd6dc5c | |||
| c7e3cb5b35 | |||
| e4d77d66ba | |||
| 9e36e86c14 | |||
| 6f6c308d84 | |||
| f429d72882 | |||
| 4fda7587ce | |||
| 3fb182b9cb | |||
| c1430594cf | |||
| 8e2ef58b81 | |||
| b4b84d0cc5 | |||
| 1d8fb8d32a | |||
| cc0cb0dd8a | |||
| fd9a5b51ba | |||
| b9a798d0ad | |||
| a1eadc0bc4 | |||
| ea53f97312 | |||
| 08bd33b4d4 | |||
| 2b5926e533 | |||
| 1e27875dc3 | |||
| 1566d60fd4 | |||
| ab99e24f64 | 
							
								
								
									
										9
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | { | ||||||
|  |     "recommendations": [ | ||||||
|  |         "rust-lang.rust-analyzer", | ||||||
|  |         "tamasfe.even-better-toml", | ||||||
|  |         "serayuzgur.crates", | ||||||
|  |         "vadimcn.vscode-lldb", | ||||||
|  |         "donaldhays.rgbds-z80" | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										2937
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2937
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										30
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -2,23 +2,27 @@ | |||||||
| name = "gb" | 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 = "2021" | ||||||
|  |  | ||||||
| # 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 | ||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| anyhow = "^1.0" | anyhow = "1.0" | ||||||
| bitfield = "^0.13" | bitfield = "0.17" | ||||||
| clap = "^2.33" | clap = { version = "4.0", features = ["cargo"] } | ||||||
| gilrs = "^0.8" | gilrs = "0.11" | ||||||
| pixels = "^0.3" | winit = "0.28" | ||||||
| winit = "^0.24" | egui = "0.21" | ||||||
| winit_input_helper = "^0.9" | egui_wgpu_backend = "0.22" | ||||||
| egui = "^0.10" | egui_winit_platform = "0.18" | ||||||
| egui_wgpu_backend = { git="https://github.com/hasenbanck/egui_wgpu_backend.git", rev="9d03ad345d15d1e44165849b242d3562fdf3e859" } | pollster = "0.3" | ||||||
| egui_winit_platform = { git="https://github.com/hasenbanck/egui_winit_platform.git", rev="17298250e9721e8bf2c1d4a17b3e22777f8cb2e8" } | rodio = "0.19" | ||||||
| rodio = "^0.14" | rtrb = "0.3" | ||||||
| crossbeam-channel = "^0.5" | directories-next = "2.0" | ||||||
|  | tracing = "0.1" | ||||||
|  | tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } | ||||||
|  | thiserror = "1.0" | ||||||
|  | once_cell = "1.10" | ||||||
|  |  | ||||||
| [profile.release] | [profile.release] | ||||||
| debug = true | debug = true | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,14 +1,43 @@ | |||||||
| # Rekai's Gameboy Emulator | # Rekai's Gameboy Emulator | ||||||
| [](https://ci.paoda.moe/paoda/gb) |  | ||||||
|  |  | ||||||
| ### Status | ### Status | ||||||
| * Passes Blargg's cpu_instrs Test | * From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/) | ||||||
| * Renders Background & Window Tiles  |     * [x] cpu_instrs | ||||||
| * Implements a PPU FIFO |     * [x] instr_timing | ||||||
|  |     * [x] mem_timing | ||||||
|  |     * [x] mem_timing-2 | ||||||
|  |     * [ ] dmg_sound (partial)  | ||||||
|  | * [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) | ||||||
|  | * From [mooneye-gb](https://github.com/Gekkio/mooneye-gb): | ||||||
|  |     * Cartridges: | ||||||
|  |         * [x] MBC1 | ||||||
|  |         * [ ] MBC1M | ||||||
|  |         * [x] MBC2 | ||||||
|  |         * [x] MBC5 | ||||||
|  | * Implements a cycle-accurate PPU FIFO | ||||||
|  |     * Doesn't \*exactly\* work just yet | ||||||
|  |  | ||||||
|  | Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games. | ||||||
|  |  | ||||||
|  |  | ||||||
| ### Notes | ### Compiling | ||||||
| * [gameboy-logs](https://github.com/wheremyfoodat/Gameboy-logs) suggests that there are still some underlying problems with the cpu implementation | This project was last successfully built on [Rust 1.64.0](https://github.com/rust-lang/rust/tree/1.64.0) | ||||||
| * The Sprite FIFO does not work as expected yet |  | ||||||
| * Sound is neither emulated nor stubbed. Upon writing / reading to a APU related register the emulator will panic.  | 1. `git clone https://github.com/paoda/gb` | ||||||
| * Code cleanup is pending completion of some minimum viable product of the emulator | 2. `cd gb` | ||||||
|  | 3. `cargo run --release` | ||||||
|  |  | ||||||
|  | ### Controls | ||||||
|  | Controls are defined [here](https://github.com/paoda/gb/blob/85940c874460b9cb31bf9b8211bf7dda596d114a/src/joypad.rs#L114) | ||||||
|  |  | ||||||
|  | Key | Button | ||||||
|  | --- | --- | ||||||
|  | <kbd>X</kbd> | B | ||||||
|  | <kbd>Z</kbd> | A | ||||||
|  | <kbd>Enter</kbd> | START | ||||||
|  | <kbd>Shift</kbd> | SELECT | ||||||
|  |  | ||||||
|  | Then use the Arrow keys for the D-Pad | ||||||
|  |  | ||||||
|  | ### Credits | ||||||
|  | The Boot ROM found in the `bin/` directory was made by [Optix](https://github.com/Hacktix) over [here](https://github.com/Hacktix/Bootix) | ||||||
							
								
								
									
										
											BIN
										
									
								
								bin/bootix_dmg.bin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/bootix_dmg.bin
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1046
									
								
								src/apu.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1046
									
								
								src/apu.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										80
									
								
								src/apu/gen.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/apu/gen.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | use rodio::Source; | ||||||
|  | use rtrb::{Consumer, Producer, PushError, RingBuffer}; | ||||||
|  |  | ||||||
|  | pub(crate) const SAMPLE_RATE: u32 = 48000; // Hz | ||||||
|  | const CHANNEL_COUNT: usize = 2; | ||||||
|  | const BUFFER_CAPACITY: usize = 2048 * CHANNEL_COUNT; // # of samples * the # of channels | ||||||
|  |  | ||||||
|  | pub fn init<T>() -> (SampleProducer<T>, SampleConsumer<T>) { | ||||||
|  |     let (prod, cons) = RingBuffer::new(BUFFER_CAPACITY); | ||||||
|  |     ( | ||||||
|  |         SampleProducer { inner: prod }, | ||||||
|  |         SampleConsumer { inner: cons }, | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct SampleProducer<T> { | ||||||
|  |     inner: Producer<T>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T> SampleProducer<T> { | ||||||
|  |     pub(crate) fn push(&mut self, value: T) -> Result<(), PushError<T>> { | ||||||
|  |         self.inner.push(value) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[allow(dead_code)] | ||||||
|  |     pub(crate) fn available(&self) -> bool { | ||||||
|  |         self.inner.slots() > 2 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn available_blocking(&self) -> bool { | ||||||
|  |         loop { | ||||||
|  |             if self.inner.slots() > 2 { | ||||||
|  |                 break true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T> std::fmt::Debug for SampleProducer<T> { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         f.debug_struct(&format!("SampleProducer<{}>", std::any::type_name::<T>())) | ||||||
|  |             .finish_non_exhaustive() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct SampleConsumer<T> { | ||||||
|  |     inner: Consumer<T>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Iterator for SampleConsumer<f32> { | ||||||
|  |     type Item = f32; | ||||||
|  |  | ||||||
|  |     fn next(&mut self) -> Option<Self::Item> { | ||||||
|  |         // As of 2021-07-28, PopError can only be Empty | ||||||
|  |         Some(self.inner.pop().unwrap_or_default()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Source for SampleConsumer<f32> { | ||||||
|  |     fn current_frame_len(&self) -> Option<usize> { | ||||||
|  |         // A frame changes when the samples rate or | ||||||
|  |         // number of channels change. This will never happen, so | ||||||
|  |         // we return | ||||||
|  |         None | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn channels(&self) -> u16 { | ||||||
|  |         // The Gameboy supports two channels | ||||||
|  |         CHANNEL_COUNT as u16 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn sample_rate(&self) -> u32 { | ||||||
|  |         SAMPLE_RATE | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn total_duration(&self) -> Option<std::time::Duration> { | ||||||
|  |         // The duration of this source is infinite | ||||||
|  |         None | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										582
									
								
								src/apu/types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								src/apu/types.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,582 @@ | |||||||
|  | use bitfield::bitfield; | ||||||
|  |  | ||||||
|  | pub(crate) trait NRx4 { | ||||||
|  |     fn trigger(&self) -> bool; | ||||||
|  |     fn length_enable(&self) -> bool; | ||||||
|  |     fn set_length_enable(&mut self, value: bool); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub(crate) mod ch1 { | ||||||
|  |     use super::bitfield; | ||||||
|  |  | ||||||
|  |     bitfield! { | ||||||
|  |         pub struct Sweep(u8); | ||||||
|  |         impl Debug; | ||||||
|  |         _period, _: 6, 4; | ||||||
|  |         from into SweepDirection, _direction, _: 3, 3; | ||||||
|  |         _shift_count, _: 2, 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Sweep { | ||||||
|  |         pub(crate) fn period(&self) -> u8 { | ||||||
|  |             self._period() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn shift_count(&self) -> u8 { | ||||||
|  |             self._shift_count() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn direction(&self) -> SweepDirection { | ||||||
|  |             self._direction() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Copy for Sweep {} | ||||||
|  |     impl Clone for Sweep { | ||||||
|  |         fn clone(&self) -> Self { | ||||||
|  |             *self | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for Sweep { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self(0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for Sweep { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             Self(byte) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<Sweep> for u8 { | ||||||
|  |         fn from(sweep: Sweep) -> Self { | ||||||
|  |             sweep.0 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[derive(Debug, Clone, Copy)] | ||||||
|  |     pub(crate) enum SweepDirection { | ||||||
|  |         Increase = 0, | ||||||
|  |         Decrease = 1, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for SweepDirection { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             match byte & 0x01 { | ||||||
|  |                 0b00 => Self::Increase, | ||||||
|  |                 0b01 => Self::Decrease, | ||||||
|  |                 _ => unreachable!("{:04X} is not a valid value for SweepChange", byte), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<SweepDirection> for u8 { | ||||||
|  |         fn from(change: SweepDirection) -> Self { | ||||||
|  |             change as u8 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub(super) mod ch3 { | ||||||
|  |     #[derive(Debug, Clone, Copy)] | ||||||
|  |     pub(crate) enum Volume { | ||||||
|  |         Mute = 0, | ||||||
|  |         Full = 1, | ||||||
|  |         Half = 2, | ||||||
|  |         Quarter = 3, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Volume { | ||||||
|  |         pub(crate) fn shift_count(&self) -> u8 { | ||||||
|  |             use Volume::*; | ||||||
|  |  | ||||||
|  |             match *self { | ||||||
|  |                 Mute => 4, | ||||||
|  |                 Full => 0, | ||||||
|  |                 Half => 1, | ||||||
|  |                 Quarter => 2, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for Volume { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self::Mute | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub(super) mod ch4 { | ||||||
|  |     use super::{bitfield, NRx4}; | ||||||
|  |  | ||||||
|  |     bitfield! { | ||||||
|  |         pub struct PolynomialCounter(u8); | ||||||
|  |         impl Debug; | ||||||
|  |         _shift_count, _: 7, 4; | ||||||
|  |         from into CounterWidth, _counter_width, _: 3, 3; | ||||||
|  |         _divisor_code, _: 2, 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl PolynomialCounter { | ||||||
|  |         pub(crate) fn divisor_code(&self) -> u8 { | ||||||
|  |             self._divisor_code() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn counter_width(&self) -> CounterWidth { | ||||||
|  |             self._counter_width() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn shift_count(&self) -> u8 { | ||||||
|  |             self._shift_count() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Copy for PolynomialCounter {} | ||||||
|  |     impl Clone for PolynomialCounter { | ||||||
|  |         fn clone(&self) -> Self { | ||||||
|  |             *self | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for PolynomialCounter { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self(0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for PolynomialCounter { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             Self(byte) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<PolynomialCounter> for u8 { | ||||||
|  |         fn from(poly: PolynomialCounter) -> Self { | ||||||
|  |             poly.0 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[derive(Debug, Clone, Copy)] | ||||||
|  |     pub(crate) enum CounterWidth { | ||||||
|  |         Long,  // 15 bits long | ||||||
|  |         Short, // 7 bits long | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for CounterWidth { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             match byte & 0x01 { | ||||||
|  |                 0b00 => Self::Short, | ||||||
|  |                 0b01 => Self::Long, | ||||||
|  |                 _ => unreachable!("{:#04X} is not a valid value for CounterWidth", byte), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<CounterWidth> for u8 { | ||||||
|  |         fn from(counter_width: CounterWidth) -> Self { | ||||||
|  |             counter_width as u8 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bitfield! { | ||||||
|  |         pub struct Frequency(u8); | ||||||
|  |         impl Debug; | ||||||
|  |         _trigger, _: 7; | ||||||
|  |         _length_enable, _set_length_enable: 6; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl NRx4 for Frequency { | ||||||
|  |         fn trigger(&self) -> bool { | ||||||
|  |             self._trigger() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn length_enable(&self) -> bool { | ||||||
|  |             self._length_enable() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn set_length_enable(&mut self, value: bool) { | ||||||
|  |             self._set_length_enable(value); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Copy for Frequency {} | ||||||
|  |     impl Clone for Frequency { | ||||||
|  |         fn clone(&self) -> Self { | ||||||
|  |             *self | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for Frequency { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self(0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for Frequency { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             Self(byte & 0xC0) // Only bits 7 and 6 hold anything of value | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<Frequency> for u8 { | ||||||
|  |         fn from(state: Frequency) -> Self { | ||||||
|  |             state.0 & 0x40 // Only bit 6 holds anything of value | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub(super) mod common { | ||||||
|  |     use super::{bitfield, NRx4}; | ||||||
|  |  | ||||||
|  |     bitfield! { | ||||||
|  |         pub struct FrequencyHigh(u8); | ||||||
|  |         impl Debug; | ||||||
|  |         _trigger, _: 7; | ||||||
|  |         _length_enable, _set_length_enable: 6; | ||||||
|  |         pub freq_bits, set_freq_bits: 2, 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl NRx4 for FrequencyHigh { | ||||||
|  |         fn trigger(&self) -> bool { | ||||||
|  |             self._trigger() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn length_enable(&self) -> bool { | ||||||
|  |             self._length_enable() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         fn set_length_enable(&mut self, value: bool) { | ||||||
|  |             self._set_length_enable(value); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Copy for FrequencyHigh {} | ||||||
|  |     impl Clone for FrequencyHigh { | ||||||
|  |         fn clone(&self) -> Self { | ||||||
|  |             *self | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for FrequencyHigh { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self(0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for FrequencyHigh { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             Self(byte) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<FrequencyHigh> for u8 { | ||||||
|  |         fn from(freq: FrequencyHigh) -> Self { | ||||||
|  |             freq.0 & 0x40 // Only bit 6 can be read | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bitfield! { | ||||||
|  |         pub struct VolumeEnvelope(u8); | ||||||
|  |         impl Debug; | ||||||
|  |         _init_vol, _: 7, 4; | ||||||
|  |         from into EnvelopeDirection, _direction, _: 3, 3; | ||||||
|  |         _period, _: 2, 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl VolumeEnvelope { | ||||||
|  |         pub(crate) fn init_vol(&self) -> u8 { | ||||||
|  |             self._init_vol() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn direction(&self) -> EnvelopeDirection { | ||||||
|  |             self._direction() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn period(&self) -> u8 { | ||||||
|  |             self._period() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Copy for VolumeEnvelope {} | ||||||
|  |     impl Clone for VolumeEnvelope { | ||||||
|  |         fn clone(&self) -> Self { | ||||||
|  |             *self | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for VolumeEnvelope { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self(0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for VolumeEnvelope { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             Self(byte) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<VolumeEnvelope> for u8 { | ||||||
|  |         fn from(envelope: VolumeEnvelope) -> Self { | ||||||
|  |             envelope.0 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[derive(Debug, Clone, Copy)] | ||||||
|  |     pub(crate) enum EnvelopeDirection { | ||||||
|  |         Decrease = 0, | ||||||
|  |         Increase = 1, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for EnvelopeDirection { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             match byte & 0b01 { | ||||||
|  |                 0b00 => Self::Decrease, | ||||||
|  |                 0b01 => Self::Increase, | ||||||
|  |                 _ => unreachable!("{:#04X} is not a valid value for EnvelopeDirection", byte), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<EnvelopeDirection> for u8 { | ||||||
|  |         fn from(direction: EnvelopeDirection) -> Self { | ||||||
|  |             direction as u8 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for EnvelopeDirection { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self::Decrease | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bitfield! { | ||||||
|  |         pub struct SoundDuty(u8); | ||||||
|  |         impl Debug; | ||||||
|  |         from into WavePattern, _wave_pattern, _: 7, 6; | ||||||
|  |         _sound_length, _: 5, 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl SoundDuty { | ||||||
|  |         pub(crate) fn wave_pattern(&self) -> WavePattern { | ||||||
|  |             self._wave_pattern() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn sound_length(&self) -> u8 { | ||||||
|  |             self._sound_length() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Copy for SoundDuty {} | ||||||
|  |     impl Clone for SoundDuty { | ||||||
|  |         fn clone(&self) -> Self { | ||||||
|  |             *self | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for SoundDuty { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self(0) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for SoundDuty { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             Self(byte) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<SoundDuty> for u8 { | ||||||
|  |         fn from(duty: SoundDuty) -> Self { | ||||||
|  |             duty.0 & 0xC0 // Only bits 7 and 6 can be read | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[derive(Debug, Clone, Copy)] | ||||||
|  |     pub(crate) enum WavePattern { | ||||||
|  |         OneEighth = 0,     // 12.5% ( _-------_-------_------- ) | ||||||
|  |         OneQuarter = 1,    // 25%   ( __------__------__------ ) | ||||||
|  |         OneHalf = 2,       // 50%   ( ____----____----____---- ) (normal) | ||||||
|  |         ThreeQuarters = 3, // 75%   ( ______--______--______-- ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl WavePattern { | ||||||
|  |         pub(crate) const fn amplitude(&self, index: u8) -> u8 { | ||||||
|  |             use WavePattern::*; | ||||||
|  |             let i = 7 - index; // an index of 0 should get the highest bit | ||||||
|  |  | ||||||
|  |             match *self { | ||||||
|  |                 OneEighth => (0b00000001 >> i) & 0x01, | ||||||
|  |                 OneQuarter => (0b10000001 >> i) & 0x01, | ||||||
|  |                 OneHalf => (0b10000111 >> i) & 0x01, | ||||||
|  |                 ThreeQuarters => (0b01111110 >> i) & 0x01, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<WavePattern> for u8 { | ||||||
|  |         fn from(pattern: WavePattern) -> Self { | ||||||
|  |             pattern as Self | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl From<u8> for WavePattern { | ||||||
|  |         fn from(byte: u8) -> Self { | ||||||
|  |             match byte & 0b11 { | ||||||
|  |                 0b00 => Self::OneEighth, | ||||||
|  |                 0b01 => Self::OneQuarter, | ||||||
|  |                 0b10 => Self::OneHalf, | ||||||
|  |                 0b11 => Self::ThreeQuarters, | ||||||
|  |                 _ => unreachable!("{:#04X} is not a valid value for WavePattern", byte), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bitfield! { | ||||||
|  |     pub struct SoundOutput(u8); | ||||||
|  |     impl Debug; | ||||||
|  |     pub ch4_left, _: 7; | ||||||
|  |     pub ch3_left, _: 6; | ||||||
|  |     pub ch2_left, _: 5; | ||||||
|  |     pub ch1_left, _: 4; | ||||||
|  |     pub ch4_right, _: 3; | ||||||
|  |     pub ch3_right, _: 2; | ||||||
|  |     pub ch2_right, _: 1; | ||||||
|  |     pub ch1_right, _: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl SoundOutput { | ||||||
|  |     pub(super) fn ch1(&self) -> (bool, bool) { | ||||||
|  |         (self.ch1_left(), self.ch1_right()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(super) fn ch2(&self) -> (bool, bool) { | ||||||
|  |         (self.ch2_left(), self.ch2_right()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(super) fn ch3(&self) -> (bool, bool) { | ||||||
|  |         (self.ch3_left(), self.ch3_right()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(super) fn ch4(&self) -> (bool, bool) { | ||||||
|  |         (self.ch4_left(), self.ch4_right()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Copy for SoundOutput {} | ||||||
|  | impl Clone for SoundOutput { | ||||||
|  |     fn clone(&self) -> Self { | ||||||
|  |         *self | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<u8> for SoundOutput { | ||||||
|  |     fn from(byte: u8) -> Self { | ||||||
|  |         Self(byte) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<SoundOutput> for u8 { | ||||||
|  |     fn from(output: SoundOutput) -> Self { | ||||||
|  |         output.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bitfield! { | ||||||
|  |     pub struct ChannelControl(u8); | ||||||
|  |     impl Debug; | ||||||
|  |     vin_left, _: 7; | ||||||
|  |     _left_volume, _: 6, 4; | ||||||
|  |     vin_right, _: 3; | ||||||
|  |     _right_volume, _: 2, 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl ChannelControl { | ||||||
|  |     pub(crate) fn left_volume(&self) -> f32 { | ||||||
|  |         self._left_volume() as f32 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn right_volume(&self) -> f32 { | ||||||
|  |         self._right_volume() as f32 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Copy for ChannelControl {} | ||||||
|  | impl Clone for ChannelControl { | ||||||
|  |     fn clone(&self) -> Self { | ||||||
|  |         *self | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<u8> for ChannelControl { | ||||||
|  |     fn from(byte: u8) -> Self { | ||||||
|  |         Self(byte) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl From<ChannelControl> for u8 { | ||||||
|  |     fn from(ctrl: ChannelControl) -> Self { | ||||||
|  |         ctrl.0 | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub(super) mod fs { | ||||||
|  |     #[derive(Debug)] | ||||||
|  |     pub(crate) struct FrameSequencer { | ||||||
|  |         step: u8, | ||||||
|  |         state: State, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl Default for FrameSequencer { | ||||||
|  |         fn default() -> Self { | ||||||
|  |             Self { | ||||||
|  |                 step: Default::default(), | ||||||
|  |                 state: State::Length, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl FrameSequencer { | ||||||
|  |         pub(crate) fn next(&mut self) { | ||||||
|  |             use State::*; | ||||||
|  |  | ||||||
|  |             self.step = (self.step + 1) % 8; | ||||||
|  |             self.state = match self.step { | ||||||
|  |                 1 | 3 | 5 => Nothing, | ||||||
|  |                 0 | 4 => Length, | ||||||
|  |                 2 | 6 => LengthAndSweep, | ||||||
|  |                 7 => Envelope, | ||||||
|  |                 _ => unreachable!("Step {} is invalid for the Frame Sequencer", self.step), | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn next_clocks_length(&self) -> bool { | ||||||
|  |             use State::*; | ||||||
|  |  | ||||||
|  |             match self.state { | ||||||
|  |                 Length | LengthAndSweep => true, | ||||||
|  |                 Nothing | Envelope => false, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn state(&self) -> State { | ||||||
|  |             self.state | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         pub(crate) fn reset(&mut self) { | ||||||
|  |             self.step = Default::default(); | ||||||
|  |             self.state = State::Length; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
|  |     pub(crate) enum State { | ||||||
|  |         Length, | ||||||
|  |         Nothing, | ||||||
|  |         LengthAndSweep, | ||||||
|  |         Envelope, | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										202
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								src/bus.rs
									
									
									
									
									
								
							| @@ -1,88 +1,64 @@ | |||||||
|  | use crate::apu::Apu; | ||||||
| use crate::cartridge::Cartridge; | use crate::cartridge::Cartridge; | ||||||
| use crate::high_ram::HighRam; | use crate::high_ram::HighRam; | ||||||
| use crate::interrupt::{Interrupt, InterruptFlag}; | use crate::interrupt::{Interrupt, InterruptFlag}; | ||||||
| use crate::joypad::Joypad; | use crate::joypad::Joypad; | ||||||
| use crate::ppu::{Ppu, PpuMode}; | use crate::ppu::{Ppu, PpuMode}; | ||||||
| use crate::serial::Serial; | use crate::serial::Serial; | ||||||
| use crate::sound::{AudioSender, Sound}; |  | ||||||
| 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, Clone)] | #[derive(Debug, Default)] | ||||||
| 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>, |     pub(crate) cart: Option<Cartridge>, | ||||||
|     pub ppu: Ppu, |     pub(crate) ppu: Ppu, | ||||||
|     work_ram: WorkRam, |     work_ram: WorkRam, | ||||||
|     var_ram: VariableWorkRam, |     var_ram: VariableWorkRam, | ||||||
|     pub(crate) timer: Timer, |     timer: Timer, | ||||||
|     int: Interrupt, |     int: Interrupt, | ||||||
|     snd: Sound, |     pub(crate) apu: Apu, | ||||||
|     high_ram: HighRam, |     high_ram: HighRam, | ||||||
|     serial: Serial, |     serial: Serial, | ||||||
|     pub(crate) joypad: Joypad, |     pub(crate) joyp: Joypad, | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Default for Bus { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self { |  | ||||||
|             boot: None, |  | ||||||
|             cartridge: None, |  | ||||||
|             ppu: Default::default(), |  | ||||||
|             work_ram: Default::default(), |  | ||||||
|             var_ram: Default::default(), |  | ||||||
|             timer: Default::default(), |  | ||||||
|             int: Default::default(), |  | ||||||
|             snd: Default::default(), |  | ||||||
|             high_ram: Default::default(), |  | ||||||
|             serial: Default::default(), |  | ||||||
|             joypad: Default::default(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| 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<()> { |     #[allow(dead_code)] | ||||||
|         self.cartridge = Some(Cartridge::new(path)?); |     pub(crate) fn boot_mapped(&self) -> bool { | ||||||
|         Ok(()) |         self.boot.is_some() | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn rom_title(&self) -> Option<&str> { |  | ||||||
|         self.cartridge.as_ref()?.title() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn pass_audio_src(&mut self, sender: AudioSender) { |  | ||||||
|         self.snd.set_audio_src(sender) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn is_mpsc_still_full(&mut self) -> bool { |  | ||||||
|         self.snd.is_mpsc_still_full() |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|     pub(crate) fn clock(&mut self) { |     pub(crate) fn clock(&mut self) { | ||||||
|         self.ppu.clock(); |         self.tick(4); | ||||||
|         self.timer.clock(); |  | ||||||
|         self.snd.clock(self.timer.divider); |  | ||||||
|         self.clock_dma(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn clock_dma(&mut self) { |     #[inline] | ||||||
|         if let Some((src_addr, dest_addr)) = self.ppu.dma.clock() { |     fn tick(&mut self, limit: u8) { | ||||||
|  |         for _ in 0..limit { | ||||||
|  |             self.timer.tick(); | ||||||
|  |  | ||||||
|  |             if let Some(c) = self.cart.as_mut() { | ||||||
|  |                 c.tick() | ||||||
|  |             } | ||||||
|  |             self.ppu.tick(); | ||||||
|  |             self.apu.tick(self.timer.divider); | ||||||
|  |             self.dma_tick() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn dma_tick(&mut self) { | ||||||
|  |         if let Some((src_addr, dest_addr)) = self.ppu.dma.tick() { | ||||||
|             let byte = self.oam_read_byte(src_addr); |             let byte = self.oam_read_byte(src_addr); | ||||||
|             self.oam_write_byte(dest_addr, byte); |             self.oam_write_byte(dest_addr, byte); | ||||||
|         } |         } | ||||||
| @@ -90,7 +66,7 @@ impl Bus { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Bus { | impl Bus { | ||||||
|     pub fn oam_read_byte(&self, addr: u16) -> u8 { |     pub(crate) fn oam_read_byte(&self, addr: u16) -> u8 { | ||||||
|         match addr { |         match addr { | ||||||
|             0x0000..=0x7FFF => { |             0x0000..=0x7FFF => { | ||||||
|                 // 16KB ROM bank 00 (ends at 0x3FFF) |                 // 16KB ROM bank 00 (ends at 0x3FFF) | ||||||
| @@ -101,16 +77,16 @@ 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 => 0xFF, | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             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 => 0xFF, | ||||||
|             }, |             }, | ||||||
|             0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0 |             0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0 | ||||||
|             0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N |             0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N | ||||||
| @@ -137,7 +113,7 @@ impl Bus { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn oam_write_byte(&mut self, addr: u16, byte: u8) { |     pub(crate) fn oam_write_byte(&mut self, addr: u16, byte: u8) { | ||||||
|         self.ppu.oam.write_byte(addr, byte); |         self.ppu.oam.write_byte(addr, byte); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -154,9 +130,9 @@ 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 => 0xFF, | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0x8000..=0x9FFF => { |             0x8000..=0x9FFF => { | ||||||
| @@ -166,10 +142,10 @@ 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 => 0xFF, | ||||||
|             }, |             }, | ||||||
|             0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0 |             0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0 | ||||||
|             0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N |             0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N | ||||||
| @@ -216,33 +192,15 @@ impl BusIo for Bus { | |||||||
|                 // Every address here starts with 0xFF so we can just check the |                 // Every address here starts with 0xFF so we can just check the | ||||||
|                 // low byte to figure out which register it is |                 // low byte to figure out which register it is | ||||||
|                 match addr & 0x00FF { |                 match addr & 0x00FF { | ||||||
|                     0x00 => self.joypad.p1, |                     0x00 => self.joyp.p1, | ||||||
|                     0x01 => self.serial.next, |                     0x01 => self.serial.next, | ||||||
|                     0x02 => self.serial.ctrl.into(), |                     0x02 => self.serial.ctrl.into(), | ||||||
|                     0x04 => (self.timer.divider >> 8) as u8, |                     0x04 => (self.timer.divider >> 8) as u8, | ||||||
|                     0x05 => self.timer.counter, |                     0x05 => self.timer.tima(), | ||||||
|                     0x06 => self.timer.modulo, |                     0x06 => self.timer.modulo, | ||||||
|                     0x07 => self.timer.ctrl.into(), |                     0x07 => self.timer.ctrl.into(), | ||||||
|                     0x0F => self.interrupt_flag().into(), |                     0x0F => self.interrupt_flag().into(), | ||||||
|                     0x10 => self.snd.ch1.sweep.into(), |                     0x10..=0x3F => self.apu.read_byte(addr), | ||||||
|                     0x11 => self.snd.ch1.duty(), |  | ||||||
|                     0x12 => self.snd.ch1.envelope.into(), |  | ||||||
|                     0x14 => self.snd.ch1.freq_hi(), |  | ||||||
|                     0x16 => self.snd.ch2.duty(), |  | ||||||
|                     0x17 => self.snd.ch2.envelope.into(), |  | ||||||
|                     0x19 => self.snd.ch2.freq_hi(), |  | ||||||
|                     0x1A => self.snd.ch3.enabled(), |  | ||||||
|                     0x1B => self.snd.ch3.len(), |  | ||||||
|                     0x1C => self.snd.ch3.volume(), |  | ||||||
|                     0x1E => self.snd.ch3.freq_hi(), |  | ||||||
|                     0x20 => self.snd.ch4.len(), |  | ||||||
|                     0x21 => self.snd.ch4.envelope.into(), |  | ||||||
|                     0x22 => self.snd.ch4.poly.into(), |  | ||||||
|                     0x23 => self.snd.ch4.freq_data(), |  | ||||||
|                     0x24 => self.snd.ctrl.channel.into(), |  | ||||||
|                     0x25 => self.snd.ctrl.output.into(), |  | ||||||
|                     0x26 => self.snd.ctrl.status(&self.snd), |  | ||||||
|                     0x30..=0x3F => self.snd.ch3.wave_ram[addr as usize - 0xFF30], |  | ||||||
|                     0x40 => self.ppu.ctrl.into(), |                     0x40 => self.ppu.ctrl.into(), | ||||||
|                     0x41 => self.ppu.stat.into(), |                     0x41 => self.ppu.stat.into(), | ||||||
|                     0x42 => self.ppu.pos.scroll_y, |                     0x42 => self.ppu.pos.scroll_y, | ||||||
| @@ -255,8 +213,11 @@ impl BusIo for Bus { | |||||||
|                     0x49 => self.ppu.monochrome.obj_palette_1.into(), |                     0x49 => self.ppu.monochrome.obj_palette_1.into(), | ||||||
|                     0x4A => self.ppu.pos.window_y, |                     0x4A => self.ppu.pos.window_y, | ||||||
|                     0x4B => self.ppu.pos.window_x, |                     0x4B => self.ppu.pos.window_x, | ||||||
|                     0x4D => 0xFF, // CGB Specific Register |                     0x4F => 0xFF, // CGB VRAM Bank Select | ||||||
|                     _ => unimplemented!("Unable to read {:#06X} in I/O Registers", addr), |                     _ => { | ||||||
|  |                         tracing::warn!("Attempted read from {:#06X} on IO", addr); | ||||||
|  |                         0xFF | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0xFF80..=0xFFFE => { |             0xFF80..=0xFFFE => { | ||||||
| @@ -275,9 +236,8 @@ 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() { |                 if let Some(cart) = self.cart.as_mut() { | ||||||
|                     Some(cart) => cart.write_byte(addr, byte), |                     cart.write_byte(addr, byte); | ||||||
|                     None => panic!("Tried to write into non-existent cartridge"), |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0x8000..=0x9FFF => { |             0x8000..=0x9FFF => { | ||||||
| @@ -289,9 +249,8 @@ impl BusIo for Bus { | |||||||
|             } |             } | ||||||
|             0xA000..=0xBFFF => { |             0xA000..=0xBFFF => { | ||||||
|                 // 8KB External RAM |                 // 8KB External RAM | ||||||
|                 match self.cartridge.as_mut() { |                 if let Some(cart) = self.cart.as_mut() { | ||||||
|                     Some(cart) => cart.write_byte(addr, byte), |                     cart.write_byte(addr, byte); | ||||||
|                     None => panic!("Tried to write into non-existent cartridge"), |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0 |             0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0 | ||||||
| @@ -324,43 +283,22 @@ impl BusIo for Bus { | |||||||
|                     _ => {} |                     _ => {} | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             0xFEA0..=0xFEFF => {} // TODO: As far as I know, writes to here do nothing. |             0xFEA0..=0xFEFF => {} // FIXME: As far as I know, writes to here do nothing. | ||||||
|             0xFF00..=0xFF7F => { |             0xFF00..=0xFF7F => { | ||||||
|                 // IO Registers |                 // IO Registers | ||||||
|  |  | ||||||
|                 // Every address here starts with 0xFF so we can just check the |                 // Every address here starts with 0xFF so we can just check the | ||||||
|                 // low byte to figure out which register it is |                 // low byte to figure out which register it is | ||||||
|                 match addr & 0x00FF { |                 match addr & 0x00FF { | ||||||
|                     0x00 => self.joypad.update(byte), |                     0x00 => self.joyp.update(byte), | ||||||
|                     0x01 => self.serial.next = byte, |                     0x01 => self.serial.next = byte, | ||||||
|                     0x02 => self.serial.ctrl = byte.into(), |                     0x02 => self.serial.ctrl = byte.into(), | ||||||
|                     0x04 => self.timer.divider = 0x0000, |                     0x04 => self.timer.divider = 0x0000, | ||||||
|                     0x05 => self.timer.counter = byte, |                     0x05 => self.timer.set_tima(byte), | ||||||
|                     0x06 => self.timer.modulo = byte, |                     0x06 => self.timer.modulo = byte, | ||||||
|                     0x07 => self.timer.ctrl = byte.into(), |                     0x07 => self.timer.ctrl = byte.into(), | ||||||
|                     0x0F => self.set_interrupt_flag(byte), |                     0x0F => self.set_interrupt_flag(byte), | ||||||
|                     0x10 => self.snd.ch1.sweep = byte.into(), |                     0x10..=0x3F => self.apu.write_byte(addr, byte), | ||||||
|                     0x11 => self.snd.ch1.set_duty(byte), |  | ||||||
|                     0x12 => self.snd.ch1.envelope = byte.into(), |  | ||||||
|                     0x13 => self.snd.ch1.set_freq_lo(byte), |  | ||||||
|                     0x14 => self.snd.ch1.set_freq_hi(byte), |  | ||||||
|                     0x16 => self.snd.ch2.set_duty(byte), |  | ||||||
|                     0x17 => self.snd.ch2.envelope = byte.into(), |  | ||||||
|                     0x18 => self.snd.ch2.set_freq_lo(byte), |  | ||||||
|                     0x19 => self.snd.ch2.set_freq_hi(byte), |  | ||||||
|                     0x1A => self.snd.ch3.set_enabled(byte), |  | ||||||
|                     0x1B => self.snd.ch3.set_len(byte), |  | ||||||
|                     0x1C => self.snd.ch3.set_volume(byte), |  | ||||||
|                     0x1D => self.snd.ch3.set_freq_lo(byte), |  | ||||||
|                     0x1E => self.snd.ch3.set_freq_hi(byte), |  | ||||||
|                     0x20 => self.snd.ch4.set_len(byte), |  | ||||||
|                     0x21 => self.snd.ch4.envelope = byte.into(), |  | ||||||
|                     0x22 => self.snd.ch4.poly = byte.into(), |  | ||||||
|                     0x23 => self.snd.ch4.set_freq_data(byte), |  | ||||||
|                     0x24 => self.snd.ctrl.channel = byte.into(), |  | ||||||
|                     0x25 => self.snd.ctrl.output = byte.into(), |  | ||||||
|                     0x26 => self.snd.ctrl.set_status(byte), // FIXME: Should we control which bytes are written to here? |  | ||||||
|                     0x30..=0x3F => self.snd.ch3.wave_ram[addr as usize - 0xFF30] = byte, |  | ||||||
|                     0x40 => self.ppu.ctrl = byte.into(), |                     0x40 => self.ppu.ctrl = byte.into(), | ||||||
|                     0x41 => self.ppu.stat.update(byte), |                     0x41 => self.ppu.stat.update(byte), | ||||||
|                     0x42 => self.ppu.pos.scroll_y = byte, |                     0x42 => self.ppu.pos.scroll_y = byte, | ||||||
| @@ -386,14 +324,15 @@ impl BusIo for Bus { | |||||||
|                     0x4A => self.ppu.pos.window_y = byte, |                     0x4A => self.ppu.pos.window_y = byte, | ||||||
|                     0x4B => self.ppu.pos.window_x = byte, |                     0x4B => self.ppu.pos.window_x = byte, | ||||||
|                     0x4D => {} // CGB Specific Register |                     0x4D => {} // CGB Specific Register | ||||||
|  |                     0x4F => {} // CGB VRAM Bank Select | ||||||
|                     0x50 => { |                     0x50 => { | ||||||
|                         // Disable Boot ROM |                         // Disable Boot ROM | ||||||
|                         if byte != 0 { |                         if byte != 0 { | ||||||
|                             self.boot = None; |                             self.boot = None; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     0x7F => {} // Tetris tries to write to this non-existent IO Address |                     0x70 => {} // CGB WRAM Bank Select | ||||||
|                     _ => unimplemented!("Unable to write to {:#06X} in I/O Registers", addr), |                     _ => tracing::warn!("Attempted write of {:#04X} to {:#06X} on IO", byte, addr), | ||||||
|                 }; |                 }; | ||||||
|             } |             } | ||||||
|             0xFF80..=0xFFFE => { |             0xFF80..=0xFFFE => { | ||||||
| @@ -408,17 +347,6 @@ impl BusIo for Bus { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Bus { |  | ||||||
|     pub(crate) fn read_word(&self, addr: u16) -> u16 { |  | ||||||
|         (self.read_byte(addr + 1) as u16) << 8 | self.read_byte(addr) as u16 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn write_word(&mut self, addr: u16, word: u16) { |  | ||||||
|         self.write_byte(addr + 1, (word >> 8) as u8); |  | ||||||
|         self.write_byte(addr, (word & 0x00FF) as u8); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Bus { | impl Bus { | ||||||
|     fn interrupt_flag(&self) -> InterruptFlag { |     fn interrupt_flag(&self) -> InterruptFlag { | ||||||
|         // Read the current interrupt information from the PPU |         // Read the current interrupt information from the PPU | ||||||
| @@ -426,7 +354,7 @@ impl Bus { | |||||||
|         let lcd_stat = self.ppu.int.lcd_stat(); |         let lcd_stat = self.ppu.int.lcd_stat(); | ||||||
|  |  | ||||||
|         // Read the current interrupt information from the Joypad |         // Read the current interrupt information from the Joypad | ||||||
|         let joypad = self.joypad.interrupt(); |         let joypad = self.joyp.interrupt(); | ||||||
|  |  | ||||||
|         // Read the current interrupt information from the Timer |         // Read the current interrupt information from the Timer | ||||||
|         let timer = self.timer.interrupt(); |         let timer = self.timer.interrupt(); | ||||||
| @@ -456,7 +384,7 @@ impl Bus { | |||||||
|         self.ppu.int.set_lcd_stat(lcd_stat); |         self.ppu.int.set_lcd_stat(lcd_stat); | ||||||
|  |  | ||||||
|         // Update the Joypad's instance of the following interrupts |         // Update the Joypad's instance of the following interrupts | ||||||
|         self.joypad.set_interrupt(joypad); |         self.joyp.set_interrupt(joypad); | ||||||
|  |  | ||||||
|         // Update the Timer's instance of the following interrupts |         // Update the Timer's instance of the following interrupts | ||||||
|         self.timer.set_interrupt(timer); |         self.timer.set_interrupt(timer); | ||||||
|   | |||||||
							
								
								
									
										1020
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										1020
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										384
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										384
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -1,54 +1,30 @@ | |||||||
| use crate::bus::{Bus, BusIo}; | use crate::bus::{Bus, BusIo, BOOT_SIZE}; | ||||||
| use crate::instruction::{Cycle, 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 crate::sound::AudioSender; |  | ||||||
| use crate::timer::Timer; |  | ||||||
| use bitfield::bitfield; | use bitfield::bitfield; | ||||||
| use std::fmt::{Display, Formatter, Result as FmtResult}; | use std::fmt::{Display, Formatter, Result as FmtResult}; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Default)] | #[derive(Debug)] | ||||||
| pub struct Cpu { | pub struct Cpu { | ||||||
|     pub bus: Bus, |     pub(crate) bus: Bus, | ||||||
|     reg: Registers, |     reg: Registers, | ||||||
|     flags: Flags, |     flags: Flags, | ||||||
|     ime: ImeState, |     ime: ImeState, | ||||||
|     // TODO: Merge halted and state properties |  | ||||||
|     halted: Option<HaltState>, |  | ||||||
|     state: State, |     state: State, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     pub fn new() -> Self { |     pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             reg: Registers { |             bus: Bus::with_boot(rom), | ||||||
|                 a: 0x01, |             reg: Default::default(), | ||||||
|                 b: 0x00, |             flags: Flags(0), | ||||||
|                 c: 0x13, |             ime: ImeState::Disabled, | ||||||
|                 d: 0x00, |             state: State::Execute, | ||||||
|                 e: 0xD8, |  | ||||||
|                 h: 0x01, |  | ||||||
|                 l: 0x4D, |  | ||||||
|                 sp: 0xFFFE, |  | ||||||
|                 pc: 0x0100, |  | ||||||
|             }, |  | ||||||
|             flags: 0xb0.into(), |  | ||||||
|             ..Default::default() |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn boot_new(path: &str) -> anyhow::Result<Self> { |  | ||||||
|         Ok(Self { |  | ||||||
|             bus: Bus::with_boot(path)?, |  | ||||||
|             ..Default::default() |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn set_audio_src(&mut self, sender: AudioSender) { |  | ||||||
|         self.bus.pass_audio_src(sender) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn ime(&self) -> ImeState { |     pub(crate) fn ime(&self) -> ImeState { | ||||||
|         self.ime |         self.ime | ||||||
|     } |     } | ||||||
| @@ -57,89 +33,100 @@ impl Cpu { | |||||||
|         self.ime = state; |         self.ime = state; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn halt(&mut self, state: HaltState) { |     pub(crate) fn halt_cpu(&mut self, kind: HaltKind) { | ||||||
|         self.halted = Some(state); |         self.state = State::Halt(kind); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn resume(&mut self) { |     fn resume_execution(&mut self) { | ||||||
|         self.halted = None; |         self.state = State::Execute; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn halted(&self) -> Option<HaltState> { |     pub(crate) fn is_halted(&self) -> bool { | ||||||
|         self.halted |         matches!(self.state, State::Halt(_)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn inc_pc(&mut self) { |     pub(crate) fn halt_kind(&self) -> Option<HaltKind> { | ||||||
|         self.reg.pc += 1; |         match self.state { | ||||||
|  |             State::Halt(kind) => Some(kind), | ||||||
|  |             _ => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|     pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { | impl Default for Cpu { | ||||||
|         self.bus.load_cartridge(path) |     fn default() -> Self { | ||||||
|     } |         Cpu::new(*include_bytes!("../bin/bootix_dmg.bin")) | ||||||
|  |  | ||||||
|     pub fn rom_title(&self) -> Option<&str> { |  | ||||||
|         self.bus.rom_title() |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     fn fetch(&self) -> u8 { |     /// Fetch an [Instruction] from the memory bus | ||||||
|         self.bus.read_byte(self.reg.pc) |     /// (4 cycles) | ||||||
|  |     fn fetch(&mut self) -> u8 { | ||||||
|  |         let byte = self.read_byte(self.reg.pc); | ||||||
|  |         self.bus.clock(); | ||||||
|  |         self.reg.pc += 1; | ||||||
|  |         byte | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Decode a byte into an [SM83](Cpu) [Instruction] | ||||||
|  |     /// | ||||||
|  |     /// If opcode == 0xCB, then decoding costs 4 cycles. | ||||||
|  |     /// Otherwise, decoding is free | ||||||
|     pub(crate) fn decode(&mut self, opcode: u8) -> Instruction { |     pub(crate) fn decode(&mut self, opcode: u8) -> Instruction { | ||||||
|         Instruction::from_byte(self, opcode) |         if opcode == 0xCB { | ||||||
|  |             Instruction::decode(self.fetch(), true) | ||||||
|  |         } else { | ||||||
|  |             Instruction::decode(opcode, false) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Execute an [Instruction]. | ||||||
|  |     /// | ||||||
|  |     /// The amount of cycles necessary to execute an instruction range from | ||||||
|  |     /// 0 to 20 T-cycles | ||||||
|     fn execute(&mut self, instruction: Instruction) -> Cycle { |     fn execute(&mut self, instruction: Instruction) -> Cycle { | ||||||
|         Instruction::execute(self, instruction) |         Instruction::execute(self, instruction) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub fn step(&mut self) -> Cycle { |     /// Perform the [`Cpu::fetch()`] [`Cpu::decode()`] [`Cpu::execute()`] | ||||||
|         // if !self.bus.boot_enabled() { |     /// routine. | ||||||
|  |     /// | ||||||
|  |     /// Handle HALT and interrupts. | ||||||
|  |     pub(crate) fn step(&mut self) -> Cycle { | ||||||
|  |         // Log instructions | ||||||
|  |         // if self.reg.pc > 0xFF { | ||||||
|         //     let out = std::io::stdout(); |         //     let out = std::io::stdout(); | ||||||
|         //     let handle = out.lock(); |         //     let _ = self._print_logs(out.lock()); | ||||||
|  |  | ||||||
|         //     self.log_state(handle).unwrap(); |  | ||||||
|         // } |         // } | ||||||
|  |  | ||||||
|         let cycles = match self.halted() { |         if let Some(elapsed) = self.handle_interrupt() { | ||||||
|             Some(state) => { |             return elapsed; | ||||||
|                 use HaltState::*; |  | ||||||
|  |  | ||||||
|                 match state { |  | ||||||
|                     ImeEnabled | NonePending => Cycle::new(4), |  | ||||||
|                     SomePending => todo!("Implement HALT bug"), |  | ||||||
|         } |         } | ||||||
|             } |  | ||||||
|             None => { |  | ||||||
|                 let opcode = self.fetch(); |  | ||||||
|                 self.inc_pc(); |  | ||||||
|  |  | ||||||
|                 let instr = self.decode(opcode); |         if let Some(kind) = self.halt_kind() { | ||||||
|                 let cycles = self.execute(instr); |             use HaltKind::*; | ||||||
|  |  | ||||||
|                 self.check_ime(); |  | ||||||
|  |  | ||||||
|                 cycles |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         // FIXME: Regression, this is not a proper fix, |  | ||||||
|         // the FIFO behaves weirdly using this code |  | ||||||
|         let pending: u32 = cycles.into(); |  | ||||||
|         let mut offset = 0; |  | ||||||
|         for _ in 0..(pending + offset) { |  | ||||||
|             if !self.bus.is_mpsc_still_full() { |  | ||||||
|             self.bus.clock(); |             self.bus.clock(); | ||||||
|             } else { |             return match kind { | ||||||
|                 offset += 1; |                 ImeEnabled | NonePending => 4, | ||||||
|             } |                 SomePending => todo!("Implement HALT bug"), | ||||||
|  |             }; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.handle_interrupts(); |         let opcode = self.fetch(); | ||||||
|  |         let instr = self.decode(opcode); | ||||||
|  |         let elapsed = self.execute(instr); | ||||||
|  |         self.handle_ei(); | ||||||
|  |  | ||||||
|         cycles |         // For use in Blargg's Test ROMs | ||||||
|  |         // if self.read_byte(0xFF02) == 0x81 { | ||||||
|  |         //     let c = self.read_byte(0xFF01) as char; | ||||||
|  |         //     self.write_byte(0xFF02, 0x00); | ||||||
|  |         //     eprint!("{}", c); | ||||||
|  |         // } | ||||||
|  |  | ||||||
|  |         elapsed | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -154,98 +141,72 @@ impl BusIo for Cpu { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     pub(crate) fn read_imm_byte(&mut self, addr: u16) -> u8 { |     fn handle_ei(&mut self) { | ||||||
|         self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 1 after this function call |  | ||||||
|         self.bus.read_byte(addr) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn read_imm_word(&mut self, addr: u16) -> u16 { |  | ||||||
|         self.inc_pc(); |  | ||||||
|         self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 2 after this function call |  | ||||||
|         self.bus.read_word(addr) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn write_word(&mut self, addr: u16, word: u16) { |  | ||||||
|         self.bus.write_word(addr, word) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Cpu { |  | ||||||
|     pub fn ppu(&mut self) -> &Ppu { |  | ||||||
|         &self.bus.ppu |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn joypad_mut(&mut self) -> &mut Joypad { |  | ||||||
|         &mut self.bus.joypad |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub(crate) fn timer(&self) -> &Timer { |  | ||||||
|         &self.bus.timer |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn check_ime(&mut self) { |  | ||||||
|         match self.ime { |         match self.ime { | ||||||
|             ImeState::Pending => { |             ImeState::EiExecuted => self.ime = ImeState::Pending, | ||||||
|                 // This is within the context of the EI instruction, we need to not update EI until the end of the |             ImeState::Pending => self.ime = ImeState::Enabled, | ||||||
|                 // next executed Instruction |             ImeState::Disabled | ImeState::Enabled => {} | ||||||
|                 self.ime = ImeState::PendingEnd; |  | ||||||
|             } |  | ||||||
|             ImeState::PendingEnd => { |  | ||||||
|                 // The Instruction after EI has now been executed, so we want to enable the IME flag here |  | ||||||
|                 self.ime = ImeState::Enabled; |  | ||||||
|             } |  | ||||||
|             ImeState::Disabled | ImeState::Enabled => {} // Do Nothing |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn handle_interrupts(&mut self) { |     pub(crate) fn int_request(&self) -> u8 { | ||||||
|         let req = self.read_byte(0xFF0F); |         self.read_byte(0xFF0F) | ||||||
|         let enabled = self.read_byte(0xFFFF); |     } | ||||||
|  |  | ||||||
|         if self.halted.is_some() { |     pub(crate) fn int_enable(&self) -> u8 { | ||||||
|  |         self.read_byte(0xFFFF) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn handle_interrupt(&mut self) -> Option<Cycle> { | ||||||
|  |         let irq = self.int_request(); | ||||||
|  |         let enable = self.int_enable(); | ||||||
|  |  | ||||||
|  |         // TODO: Ensure that this behaviour is correct | ||||||
|  |         if self.is_halted() { | ||||||
|             // When we're here either a HALT with IME set or |             // When we're here either a HALT with IME set or | ||||||
|             // a HALT with IME not set and No pending Interrupts was called |             // a HALT with IME not set and No pending Interrupts was called | ||||||
|  |  | ||||||
|             if req & enabled != 0 { |             if irq & enable != 0 { | ||||||
|                 // The if self.ime() below correctly follows the "resuming from HALT" behaviour so |                 // The if self.ime() below correctly follows the "resuming from HALT" behaviour so | ||||||
|                 // nothing actually needs to be added here. This is just documentation |                 // nothing actually needs to be added here. This is just documentation | ||||||
|                 // since it's a bit weird why nothing is being done |                 // since it's a bit weird why nothing is being done | ||||||
|  |  | ||||||
|                 self.resume() |                 self.resume_execution(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if let ImeState::Enabled = self.ime() { |         match self.ime() { | ||||||
|             let mut req: InterruptFlag = req.into(); |             ImeState::Enabled => { | ||||||
|             let enabled: InterruptEnable = enabled.into(); |                 let mut irq: InterruptFlag = irq.into(); | ||||||
|  |                 let enable: InterruptEnable = enable.into(); | ||||||
|  |  | ||||||
|             let vector = if req.vblank() && enabled.vblank() { |                 let rst_vector = if irq.vblank() && enable.vblank() { | ||||||
|                     // Handle VBlank Interrupt |                     // Handle VBlank Interrupt | ||||||
|                 req.set_vblank(false); |                     irq.set_vblank(false); | ||||||
|  |  | ||||||
|                     // INT 40h |                     // INT 40h | ||||||
|                     Some(0x40) |                     Some(0x40) | ||||||
|             } else if req.lcd_stat() && enabled.lcd_stat() { |                 } else if irq.lcd_stat() && enable.lcd_stat() { | ||||||
|                     // Handle LCD STAT Interrupt |                     // Handle LCD STAT Interrupt | ||||||
|                 req.set_lcd_stat(false); |                     irq.set_lcd_stat(false); | ||||||
|  |  | ||||||
|                     // INT 48h |                     // INT 48h | ||||||
|                     Some(0x48) |                     Some(0x48) | ||||||
|             } else if req.timer() && enabled.timer() { |                 } else if irq.timer() && enable.timer() { | ||||||
|                     // Handle Timer Interrupt |                     // Handle Timer Interrupt | ||||||
|                 req.set_timer(false); |                     irq.set_timer(false); | ||||||
|  |  | ||||||
|                     // INT 50h |                     // INT 50h | ||||||
|                     Some(0x50) |                     Some(0x50) | ||||||
|             } else if req.serial() && enabled.serial() { |                 } else if irq.serial() && enable.serial() { | ||||||
|                     // Handle Serial Interrupt |                     // Handle Serial Interrupt | ||||||
|                 req.set_serial(false); |                     irq.set_serial(false); | ||||||
|  |  | ||||||
|                     // INT 58h |                     // INT 58h | ||||||
|                     Some(0x58) |                     Some(0x58) | ||||||
|             } else if req.joypad() && enabled.joypad() { |                 } else if irq.joypad() && enable.joypad() { | ||||||
|                     // Handle Joypad Interrupt |                     // Handle Joypad Interrupt | ||||||
|                 req.set_joypad(false); |                     irq.set_joypad(false); | ||||||
|  |  | ||||||
|                     // INT 60h |                     // INT 60h | ||||||
|                     Some(0x60) |                     Some(0x60) | ||||||
| @@ -253,34 +214,30 @@ impl Cpu { | |||||||
|                     None |                     None | ||||||
|                 }; |                 }; | ||||||
|  |  | ||||||
|             let _ = match vector { |                 match rst_vector { | ||||||
|                 Some(address) => { |                     Some(vector) => { | ||||||
|                         //  Write the Changes to 0xFF0F and 0xFFFF registers |                         //  Write the Changes to 0xFF0F and 0xFFFF registers | ||||||
|                     self.write_byte(0xFF0F, req.into()); |                         self.write_byte(0xFF0F, irq.into()); | ||||||
|  |  | ||||||
|                         // Disable all future interrupts |                         // Disable all future interrupts | ||||||
|                         self.set_ime(ImeState::Disabled); |                         self.set_ime(ImeState::Disabled); | ||||||
|                     Instruction::reset(self, address) |                         Some(Instruction::reset(self, vector)) | ||||||
|                     } |                     } | ||||||
|                 None => Cycle::new(0), // NO Interrupts were enabled and / or requested |                     None => None, | ||||||
|             }; |                 } | ||||||
|  |             } | ||||||
|  |             _ => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Copy, Clone)] | #[derive(Debug, Clone, Copy)] | ||||||
| enum State { | enum State { | ||||||
|     Execute, |     Execute, | ||||||
|     // Halt, |     Halt(HaltKind), | ||||||
|     // Stop, |     // Stop, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for State { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::Execute |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     pub(crate) fn set_register(&mut self, register: Register, value: u8) { |     pub(crate) fn set_register(&mut self, register: Register, value: u8) { | ||||||
|         use Register::*; |         use Register::*; | ||||||
| @@ -293,7 +250,6 @@ impl Cpu { | |||||||
|             E => self.reg.e = value, |             E => self.reg.e = value, | ||||||
|             H => self.reg.h = value, |             H => self.reg.h = value, | ||||||
|             L => self.reg.l = value, |             L => self.reg.l = value, | ||||||
|             Flag => self.flags = value.into(), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -308,7 +264,6 @@ impl Cpu { | |||||||
|             E => self.reg.e, |             E => self.reg.e, | ||||||
|             H => self.reg.h, |             H => self.reg.h, | ||||||
|             L => self.reg.l, |             L => self.reg.l, | ||||||
|             Flag => self.flags.into(), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -357,13 +312,20 @@ impl Cpu { | |||||||
|         &self.flags |         &self.flags | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn update_flags(&mut self, z: bool, n: bool, h: bool, c: bool) { | ||||||
|  |         self.flags.set_z(z); | ||||||
|  |         self.flags.set_n(n); | ||||||
|  |         self.flags.set_h(h); | ||||||
|  |         self.flags.set_c(c); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     pub(crate) fn set_flags(&mut self, flags: Flags) { |     pub(crate) fn set_flags(&mut self, flags: Flags) { | ||||||
|         self.flags = flags; |         self.flags = flags; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Cpu { | impl Cpu { | ||||||
|     fn _debug_log(&self, mut w: impl std::io::Write, instr: &Instruction) -> std::io::Result<()> { |     fn _print_debug(&self, mut w: impl std::io::Write) -> std::io::Result<()> { | ||||||
|         write!(w, "A: {:02X} ", self.reg.a)?; |         write!(w, "A: {:02X} ", self.reg.a)?; | ||||||
|         write!(w, "F: {:02X} ", u8::from(self.flags))?; |         write!(w, "F: {:02X} ", u8::from(self.flags))?; | ||||||
|         write!(w, "B: {:02X} ", self.reg.b)?; |         write!(w, "B: {:02X} ", self.reg.b)?; | ||||||
| @@ -378,30 +340,39 @@ impl Cpu { | |||||||
|         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?; |         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?; | ||||||
|         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?; |         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?; | ||||||
|         write!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?; |         write!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?; | ||||||
|         writeln!(w, "| {:?}", instr)?; |         writeln!(w, "| {:?}", self._dbg_instr())?; | ||||||
|         w.flush() |         w.flush() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn _log_state(&self, mut writer: impl std::io::Write) -> std::io::Result<()> { |     fn _print_logs(&self, mut w: impl std::io::Write) -> std::io::Result<()> { | ||||||
|         write!(writer, "A: {:02X} ", self.reg.a)?; |         write!(w, "A: {:02X} ", self.reg.a)?; | ||||||
|         write!(writer, "F: {:02X} ", u8::from(self.flags))?; |         write!(w, "F: {:02X} ", u8::from(self.flags))?; | ||||||
|         write!(writer, "B: {:02X} ", self.reg.b)?; |         write!(w, "B: {:02X} ", self.reg.b)?; | ||||||
|         write!(writer, "C: {:02X} ", self.reg.c)?; |         write!(w, "C: {:02X} ", self.reg.c)?; | ||||||
|         write!(writer, "D: {:02X} ", self.reg.d)?; |         write!(w, "D: {:02X} ", self.reg.d)?; | ||||||
|         write!(writer, "E: {:02X} ", self.reg.e)?; |         write!(w, "E: {:02X} ", self.reg.e)?; | ||||||
|         write!(writer, "H: {:02X} ", self.reg.h)?; |         write!(w, "H: {:02X} ", self.reg.h)?; | ||||||
|         write!(writer, "L: {:02X} ", self.reg.l)?; |         write!(w, "L: {:02X} ", self.reg.l)?; | ||||||
|         write!(writer, "SP: {:04X} ", self.reg.sp)?; |         write!(w, "SP: {:04X} ", self.reg.sp)?; | ||||||
|         write!(writer, "PC: 00:{:04X} ", self.reg.pc)?; |         write!(w, "PC: 00:{:04X} ", self.reg.pc)?; | ||||||
|         write!(writer, "({:02X} ", self.read_byte(self.reg.pc))?; |         write!(w, "({:02X} ", self.read_byte(self.reg.pc))?; | ||||||
|         write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 1))?; |         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?; | ||||||
|         write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 2))?; |         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?; | ||||||
|         writeln!(writer, "{:02X})", self.read_byte(self.reg.pc + 3))?; |         writeln!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?; | ||||||
|         writer.flush() |         w.flush() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn _dbg_instr(&self) -> Instruction { | ||||||
|  |         let byte = self.read_byte(self.reg.pc); | ||||||
|  |         if byte == 0xCB { | ||||||
|  |             Instruction::decode(self.read_byte(self.reg.pc + 1), true) | ||||||
|  |         } else { | ||||||
|  |             Instruction::decode(byte, false) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Copy, Clone)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub(crate) enum Register { | pub(crate) enum Register { | ||||||
|     A, |     A, | ||||||
|     B, |     B, | ||||||
| @@ -410,10 +381,9 @@ pub(crate) enum Register { | |||||||
|     E, |     E, | ||||||
|     H, |     H, | ||||||
|     L, |     L, | ||||||
|     Flag, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Copy, Clone)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub(crate) enum RegisterPair { | pub(crate) enum RegisterPair { | ||||||
|     AF, |     AF, | ||||||
|     BC, |     BC, | ||||||
| @@ -423,7 +393,7 @@ pub(crate) enum RegisterPair { | |||||||
|     PC, |     PC, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Copy, Clone, Default)] | #[derive(Debug, Default)] | ||||||
| struct Registers { | struct Registers { | ||||||
|     a: u8, |     a: u8, | ||||||
|     b: u8, |     b: u8, | ||||||
| @@ -461,12 +431,6 @@ impl Clone for Flags { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Flags { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Display for Flags { | impl Display for Flags { | ||||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { |     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { | ||||||
|         if self.z() { |         if self.z() { | ||||||
| @@ -508,7 +472,7 @@ impl From<u8> for Flags { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub(crate) enum HaltState { | pub(crate) enum HaltKind { | ||||||
|     ImeEnabled, |     ImeEnabled, | ||||||
|     NonePending, |     NonePending, | ||||||
|     SomePending, |     SomePending, | ||||||
| @@ -517,13 +481,43 @@ pub(crate) enum HaltState { | |||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub(crate) enum ImeState { | pub(crate) enum ImeState { | ||||||
|     Disabled, |     Disabled, | ||||||
|  |     EiExecuted, | ||||||
|     Pending, |     Pending, | ||||||
|     PendingEnd, |  | ||||||
|     Enabled, |     Enabled, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for ImeState { | pub(crate) mod dbg { | ||||||
|     fn default() -> Self { |     use super::{Cpu, ImeState, RegisterPair}; | ||||||
|         Self::Disabled |  | ||||||
|  |     pub(crate) fn flags(cpu: &Cpu) -> u8 { | ||||||
|  |         cpu.flags.into() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn af(cpu: &Cpu) -> u16 { | ||||||
|  |         cpu.register_pair(RegisterPair::AF) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn bc(cpu: &Cpu) -> u16 { | ||||||
|  |         cpu.register_pair(RegisterPair::BC) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn de(cpu: &Cpu) -> u16 { | ||||||
|  |         cpu.register_pair(RegisterPair::DE) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn hl(cpu: &Cpu) -> u16 { | ||||||
|  |         cpu.register_pair(RegisterPair::HL) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn sp(cpu: &Cpu) -> u16 { | ||||||
|  |         cpu.register_pair(RegisterPair::SP) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn pc(cpu: &Cpu) -> u16 { | ||||||
|  |         cpu.register_pair(RegisterPair::PC) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) fn ime(cpu: &Cpu) -> bool { | ||||||
|  |         matches!(cpu.ime, ImeState::Enabled) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										191
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										191
									
								
								src/emu.rs
									
									
									
									
									
								
							| @@ -1,60 +1,163 @@ | |||||||
| use crate::cpu::Cpu as SM83; | use crate::apu::gen::SampleProducer; | ||||||
| use crate::instruction::Cycle; | use crate::bus::BOOT_SIZE; | ||||||
| use crate::joypad; | use crate::cartridge::Cartridge; | ||||||
| use crate::ppu::Ppu; | use crate::cpu::Cpu; | ||||||
| use anyhow::Result; | use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; | ||||||
|  | use clap::crate_name; | ||||||
| use gilrs::Gilrs; | use gilrs::Gilrs; | ||||||
|  | use std::fs::File; | ||||||
|  | use std::io::{Read, Write}; | ||||||
|  | use std::path::{Path, PathBuf}; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
| use winit_input_helper::WinitInputHelper; | use thiserror::Error; | ||||||
|  | use winit::event::KeyboardInput; | ||||||
|  | use winit::event_loop::ControlFlow; | ||||||
|  |  | ||||||
| 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 = "Game Boy Screen"; | ||||||
| const GAMEPAD_ENABLED: bool = false; |  | ||||||
|  |  | ||||||
| pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> { | #[inline] | ||||||
|     let mut cpu = match boot_path { | pub fn run_frame(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput) { | ||||||
|         Some(path) => SM83::boot_new(path)?, |     run(cpu, gamepad, key, CYCLES_IN_FRAME) | ||||||
|         None => SM83::new(), |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     eprintln!("Initialized GB Emulator"); |  | ||||||
|  |  | ||||||
|     cpu.load_cartridge(rom_path)?; |  | ||||||
|     Ok(cpu) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn rom_title(game_boy: &SM83) -> &str { | #[inline] | ||||||
|     game_boy.rom_title().unwrap_or(DEFAULT_TITLE) | pub fn run(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput, cycles: Cycle) { | ||||||
| } |     let mut elapsed = 0; | ||||||
|  |  | ||||||
| pub fn run( |  | ||||||
|     game_boy: &mut SM83, |  | ||||||
|     gamepad: &mut Gilrs, |  | ||||||
|     input: &WinitInputHelper, |  | ||||||
|     pending: Cycle, |  | ||||||
| ) -> Cycle { |  | ||||||
|     let mut elapsed = Cycle::new(0); |  | ||||||
|  |  | ||||||
|     while elapsed < pending { |  | ||||||
|         elapsed += run_unsynced(game_boy, gamepad, input); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     elapsed |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub fn run_unsynced(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle { |  | ||||||
|     if GAMEPAD_ENABLED { |  | ||||||
|     if let Some(event) = gamepad.next_event() { |     if let Some(event) = gamepad.next_event() { | ||||||
|             joypad::handle_gamepad_input(game_boy.joypad_mut(), event); |         crate::joypad::handle_gamepad_input(&mut cpu.bus.joyp, event); | ||||||
|  |     } | ||||||
|  |     crate::joypad::handle_keyboard_input(&mut cpu.bus.joyp, key); | ||||||
|  |  | ||||||
|  |     while elapsed < cycles { | ||||||
|  |         elapsed += cpu.step(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|     joypad::handle_keyboard_input(game_boy.joypad_mut(), input); | pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) { | ||||||
|     game_boy.step() |     write_save(cpu); | ||||||
|  |     *control_flow = ControlFlow::Exit; | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn draw(ppu: &Ppu, frame: &mut [u8]) { | #[inline] | ||||||
|     ppu.copy_to_gui(frame); | pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { | ||||||
|  |     use crate::ppu::Device; | ||||||
|  |     cpu.bus.ppu.frame_buf.get(Device::Host) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> { | ||||||
|  |     Ok(Cpu::new(read_boot(path)?)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn read_game_rom<P: AsRef<Path>>(cpu: &mut Cpu, path: P) -> std::io::Result<()> { | ||||||
|  |     cpu.bus.cart = Some(Cartridge::new(std::fs::read(path.as_ref())?)); | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn set_audio_prod(cpu: &mut Cpu, prod: SampleProducer<f32>) { | ||||||
|  |     cpu.bus.apu.prod = Some(prod); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn rom_title(cpu: &Cpu) -> &str { | ||||||
|  |     cpu.bus | ||||||
|  |         .cart | ||||||
|  |         .as_ref() | ||||||
|  |         .and_then(|c| c.title.as_deref()) | ||||||
|  |         .unwrap_or(DEFAULT_TITLE) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn write_save(cpu: &Cpu) { | ||||||
|  |     match cpu.bus.cart.as_ref() { | ||||||
|  |         Some(cart) => match write_save_to_file(cart) { | ||||||
|  |             Ok(path) => tracing::info!("Wrote to save at {:?}", path), | ||||||
|  |             Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to Save: {:?}", err), | ||||||
|  |             Err(SaveError::DiffSize) => unreachable!(), | ||||||
|  |             Err(SaveError::Io(err)) => tracing::error!("{:?}", err), | ||||||
|  |         }, | ||||||
|  |         None => tracing::error!("No cartridge is currently present"), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn load_save(cpu: &mut Cpu) { | ||||||
|  |     match cpu.bus.cart.as_mut() { | ||||||
|  |         Some(cart) => match read_save_from_file(cart) { | ||||||
|  |             Ok(path) => tracing::info!("Loaded save from {:?}", path), | ||||||
|  |             Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to load save: {}", err), | ||||||
|  |             Err(err @ SaveError::DiffSize) => tracing::error!("Unable to load save: {}", err), | ||||||
|  |             Err(SaveError::Io(err)) => match err.kind() { | ||||||
|  |                 std::io::ErrorKind::NotFound => tracing::warn!("Save not found"), | ||||||
|  |                 _ => tracing::error!("{:?}", err), | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         None => tracing::error!("No cartridge is currently present"), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn write_save_to_file(cart: &Cartridge) -> Result<PathBuf, SaveError> { | ||||||
|  |     match cart.title.as_ref().zip(cart.ext_ram()) { | ||||||
|  |         Some((title, ram)) => { | ||||||
|  |             let mut save_path = data_path().unwrap_or_else(|| PathBuf::from(".")); | ||||||
|  |             save_path.push(title); | ||||||
|  |             save_path.set_extension("sav"); | ||||||
|  |  | ||||||
|  |             let mut file = File::create(&save_path)?; | ||||||
|  |             file.write_all(ram)?; | ||||||
|  |             Ok(save_path) | ||||||
|  |         } | ||||||
|  |         None => Err(SaveError::NotApplicable), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> { | ||||||
|  |     match cart.title.clone().zip(cart.ext_ram_mut()) { | ||||||
|  |         Some((title, ext_ram)) => { | ||||||
|  |             let mut save_path = data_path().unwrap_or_else(|| PathBuf::from(".")); | ||||||
|  |             save_path.push(title); | ||||||
|  |             save_path.set_extension("sav"); | ||||||
|  |  | ||||||
|  |             let mut file = File::open(&save_path)?; | ||||||
|  |             let mut memory = Vec::new(); | ||||||
|  |             file.read_to_end(&mut memory)?; | ||||||
|  |  | ||||||
|  |             if ext_ram.len() != memory.len() { | ||||||
|  |                 return Err(SaveError::DiffSize); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             ext_ram.copy_from_slice(&memory); | ||||||
|  |             Ok(save_path) | ||||||
|  |         } | ||||||
|  |         None => Err(SaveError::NotApplicable), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> { | ||||||
|  |     let mut buf = [0; BOOT_SIZE]; | ||||||
|  |     let mut file = File::open(path.as_ref())?; | ||||||
|  |  | ||||||
|  |     file.read_exact(&mut buf)?; | ||||||
|  |     Ok(buf) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn data_path() -> Option<PathBuf> { | ||||||
|  |     match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) { | ||||||
|  |         Some(dirs) => { | ||||||
|  |             let data_local = dirs.data_local_dir(); | ||||||
|  |             std::fs::create_dir_all(data_local).ok()?; | ||||||
|  |             Some(data_local.to_path_buf()) | ||||||
|  |         } | ||||||
|  |         None => None, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Error)] | ||||||
|  | pub enum SaveError { | ||||||
|  |     #[error("cartridge lacks title and/or external ram")] | ||||||
|  |     NotApplicable, | ||||||
|  |     #[error(transparent)] | ||||||
|  |     Io(#[from] std::io::Error), | ||||||
|  |     #[error("save file size differs from external ram")] | ||||||
|  |     DiffSize, | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										729
									
								
								src/gui.rs
									
									
									
									
									
								
							
							
						
						
									
										729
									
								
								src/gui.rs
									
									
									
									
									
								
							| @@ -1,321 +1,472 @@ | |||||||
| use crate::bus::BusIo; | use egui::{Context, TextureId}; | ||||||
| use crate::cpu::{Cpu as SM83, Register, RegisterPair}; | use egui_wgpu_backend::{wgpu, RenderPass, ScreenDescriptor}; | ||||||
| use egui::{ClippedMesh, FontDefinitions}; | use egui_winit_platform::Platform; | ||||||
| use egui_wgpu_backend::{RenderPass, ScreenDescriptor}; | use wgpu::{ | ||||||
| use egui_winit_platform::{Platform, PlatformDescriptor}; |     Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError, | ||||||
| use pixels::{wgpu, PixelsContext}; |     Surface, SurfaceCapabilities, SurfaceConfiguration, Texture, TextureView, | ||||||
| use std::time::Instant; |     TextureViewDescriptor, | ||||||
| use wgpu::TextureFormat::Bgra8UnormSrgb; |  | ||||||
|  |  | ||||||
| // Boilerplate code from: https://github.com/parasyte/pixels/blob/0.3.0/examples/egui-winit/src/gui.rs |  | ||||||
|  |  | ||||||
| /// Manages all state required for rendering egui over `Pixels`. |  | ||||||
| pub struct Egui { |  | ||||||
|     // State for egui. |  | ||||||
|     start_time: Instant, |  | ||||||
|     platform: Platform, |  | ||||||
|     screen_desc: ScreenDescriptor, |  | ||||||
|     render_pass: RenderPass, |  | ||||||
|     paint_jobs: Vec<ClippedMesh>, |  | ||||||
|  |  | ||||||
|     pub config: Configuration, |  | ||||||
|  |  | ||||||
|     show_flags: bool, |  | ||||||
|     show_cpu_info: bool, |  | ||||||
|     show_registers: bool, |  | ||||||
|     show_int: bool, |  | ||||||
|     show_timer: bool, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl Egui { |  | ||||||
|     /// Create egui. |  | ||||||
|     pub fn new(width: u32, height: u32, scale_factor: f64, context: &PixelsContext) -> Self { |  | ||||||
|         let platform = Platform::new(PlatformDescriptor { |  | ||||||
|             physical_width: width, |  | ||||||
|             physical_height: height, |  | ||||||
|             scale_factor, |  | ||||||
|             font_definitions: FontDefinitions::default(), |  | ||||||
|             style: Default::default(), |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         let screen_desc = ScreenDescriptor { |  | ||||||
|             physical_width: width, |  | ||||||
|             physical_height: height, |  | ||||||
|             scale_factor: scale_factor as f32, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|         let render_pass = RenderPass::new(&context.device, Bgra8UnormSrgb); | use winit::dpi::PhysicalSize; | ||||||
|  | use winit::error::OsError; | ||||||
|  | use winit::event::{ElementState, Event, KeyboardInput}; | ||||||
|  | use winit::event_loop::{ControlFlow, EventLoop}; | ||||||
|  | use winit::window::Window; | ||||||
|  |  | ||||||
|  | use crate::cpu::Cpu; | ||||||
|  | use crate::{emu, GB_HEIGHT, GB_WIDTH}; | ||||||
|  |  | ||||||
|  | const EGUI_DIMENSIONS: (usize, usize) = (1280, 720); | ||||||
|  | const FILTER_MODE: FilterMode = FilterMode::Nearest; | ||||||
|  | const WINDOW_TITLE: &str = "DMG-01 Emulator"; | ||||||
|  |  | ||||||
|  | const SCALE: f32 = 3.0; | ||||||
|  |  | ||||||
|  | pub struct Gui { | ||||||
|  |     pub should_quit: bool, | ||||||
|  |     pub title: String, | ||||||
|  |     pub mode: EmuMode, | ||||||
|  |  | ||||||
|  |     window: Window, | ||||||
|  |     platform: Platform, | ||||||
|  |  | ||||||
|  |     surface: Surface, | ||||||
|  |     device: Device, | ||||||
|  |     queue: Queue, | ||||||
|  |     surface_config: SurfaceConfiguration, | ||||||
|  |     render_pass: RenderPass, | ||||||
|  |  | ||||||
|  |     texture: Texture, | ||||||
|  |     texture_size: Extent3d, | ||||||
|  |     texture_id: TextureId, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Gui { | ||||||
|  |     pub fn new<T>(title: String, event_loop: &EventLoop<T>, cpu: &Cpu) -> Self { | ||||||
|  |         use wgpu::InstanceDescriptor; | ||||||
|  |  | ||||||
|  |         let window = build_window(event_loop).expect("build window"); | ||||||
|  |  | ||||||
|  |         let instance = Instance::new(InstanceDescriptor::default()); | ||||||
|  |         let surface = unsafe { | ||||||
|  |             instance | ||||||
|  |                 .create_surface(&window) | ||||||
|  |                 .expect("create wgpu instance surface") | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         let adapter = request_adapter(&instance, &surface).expect("request adaptor"); | ||||||
|  |         let (device, queue) = request_device(&adapter).expect("request device"); | ||||||
|  |  | ||||||
|  |         let capabilities = surface.get_capabilities(&adapter); | ||||||
|  |  | ||||||
|  |         let surface_config = surface_config(&window, capabilities); | ||||||
|  |         surface.configure(&device, &surface_config); | ||||||
|  |         let platform = platform(&window); | ||||||
|  |         let mut render_pass = RenderPass::new(&device, surface_config.format, 1); | ||||||
|  |  | ||||||
|  |         let texture_size = texture_size(); | ||||||
|  |         let texture = create_texture(&device, texture_size); | ||||||
|  |         write_to_texture(&queue, &texture, emu::pixel_buf(cpu), texture_size); | ||||||
|  |  | ||||||
|  |         let view = texture.create_view(&TextureViewDescriptor::default()); | ||||||
|  |         let texture_id = expose_texture_to_egui(&mut render_pass, &device, &view); | ||||||
|  |  | ||||||
|         Self { |         Self { | ||||||
|             start_time: Instant::now(), |             should_quit: Default::default(), | ||||||
|  |             title, | ||||||
|  |             mode: EmuMode::Running, | ||||||
|  |  | ||||||
|  |             window, | ||||||
|             platform, |             platform, | ||||||
|             screen_desc, |  | ||||||
|  |             surface, | ||||||
|  |             device, | ||||||
|  |             queue, | ||||||
|  |             surface_config, | ||||||
|             render_pass, |             render_pass, | ||||||
|             paint_jobs: Vec::new(), |  | ||||||
|             config: Default::default(), |             texture, | ||||||
|             show_flags: false, |             texture_size, | ||||||
|             show_cpu_info: false, |             texture_id, | ||||||
|             show_registers: false, |  | ||||||
|             show_int: false, |  | ||||||
|             show_timer: false, |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Handle input events from the window manager. |     pub fn maybe_quit(&self, cpu: &Cpu, control_flow: &mut ControlFlow) { | ||||||
|     pub fn handle_event(&mut self, event: &winit::event::Event<'_, ()>) { |         if self.should_quit { | ||||||
|  |             emu::save_and_exit(cpu, control_flow); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn request_redraw(&self) { | ||||||
|  |         self.window.request_redraw(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn handle_event<T>(&mut self, event: &Event<T>) { | ||||||
|         self.platform.handle_event(event); |         self.platform.handle_event(event); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Resize egui. |     pub fn update_time(&mut self, elapsed_seconds: f64) { | ||||||
|     pub fn resize(&mut self, width: u32, height: u32) { |         self.platform.update_time(elapsed_seconds); | ||||||
|         self.screen_desc.physical_width = width; |  | ||||||
|         self.screen_desc.physical_height = height; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Update scaling factor. |     pub fn resize(&mut self, size: PhysicalSize<u32>) { | ||||||
|     pub fn scale_factor(&mut self, scale_factor: f64) { |         // See: https://github.com/rust-windowing/winit/issues/208 | ||||||
|         self.screen_desc.scale_factor = scale_factor as f32; |         if size.width > 0 && size.height > 0 { | ||||||
|  |             self.surface_config.width = size.width; | ||||||
|  |             self.surface_config.height = size.height; | ||||||
|  |             self.surface.configure(&self.device, &self.surface_config); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Prepare egui. |     pub fn paint(&mut self, cpu: &Cpu) { | ||||||
|     pub fn prepare(&mut self, game_boy: &SM83) { |         use wgpu::{Color, SurfaceError}; | ||||||
|         self.platform |  | ||||||
|             .update_time(self.start_time.elapsed().as_secs_f64()); |  | ||||||
|  |  | ||||||
|         // Begin the egui frame. |         let data = emu::pixel_buf(cpu); | ||||||
|  |         write_to_texture(&self.queue, &self.texture, data, self.texture_size); | ||||||
|  |  | ||||||
|  |         let output_frame = match self.surface.get_current_texture() { | ||||||
|  |             Ok(frame) => frame, | ||||||
|  |             Err(SurfaceError::Outdated) => return, // Occurs on minimization on Windows | ||||||
|  |             Err(e) => { | ||||||
|  |                 eprintln!("Dropped frame with error: {}", e); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         let output_view = output_frame | ||||||
|  |             .texture | ||||||
|  |             .create_view(&TextureViewDescriptor::default()); | ||||||
|  |  | ||||||
|  |         // Begin to draw Egui components | ||||||
|         self.platform.begin_frame(); |         self.platform.begin_frame(); | ||||||
|  |         self.draw_egui(cpu, &self.platform.context(), self.texture_id); | ||||||
|  |  | ||||||
|         // Draw the demo application. |         // End the UI frame. We could now handle the output and draw the UI with the backend. | ||||||
|         self.ui(&self.platform.context(), game_boy); |         let full_output = self.platform.end_frame(Some(&self.window)); | ||||||
|  |         let paint_jobs = self.platform.context().tessellate(full_output.shapes); | ||||||
|  |  | ||||||
|         // End the egui frame and create all paint jobs to prepare for rendering. |         let mut encoder = create_command_encoder(&self.device); | ||||||
|         let (_output, paint_commands) = self.platform.end_frame(); |         let screen_descriptor = create_screen_descriptor(&self.window, &self.surface_config); | ||||||
|         self.paint_jobs = self.platform.context().tessellate(paint_commands); |         let tdelta = full_output.textures_delta; | ||||||
|     } |         // Upload all resources for the GPU. | ||||||
|  |  | ||||||
|     /// Create the UI using egui. |  | ||||||
|     fn ui(&mut self, ctx: &egui::CtxRef, game_boy: &SM83) { |  | ||||||
|         egui::TopPanel::top("menubar_container").show(ctx, |ui| { |  | ||||||
|             egui::menu::bar(ui, |ui| { |  | ||||||
|                 egui::menu::menu(ui, "File", |ui| { |  | ||||||
|                     if ui.button("Configuration").clicked() { |  | ||||||
|                         self.config.show = true; |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 egui::menu::menu(ui, "Status", |ui| { |  | ||||||
|                     if ui.button("Flags").clicked() { |  | ||||||
|                         self.show_flags = true; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if ui.button("CPU Information").clicked() { |  | ||||||
|                         self.show_cpu_info = true; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if ui.button("Registers").clicked() { |  | ||||||
|                         self.show_registers = true; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if ui.button("Interrupts").clicked() { |  | ||||||
|                         self.show_int = true; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if ui.button("Timer").clicked() { |  | ||||||
|                         self.show_timer = true; |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         egui::Window::new("Cpu Flags") |  | ||||||
|             .open(&mut self.show_flags) |  | ||||||
|             .show(ctx, |ui| { |  | ||||||
|                 let flags = game_boy.flags(); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     let _ = ui.selectable_label(flags.z(), "Zero"); |  | ||||||
|                     let _ = ui.selectable_label(flags.n(), "Negative"); |  | ||||||
|                     let _ = ui.selectable_label(flags.h(), "Half-Carry"); |  | ||||||
|                     let _ = ui.selectable_label(flags.c(), "Carry"); |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         egui::Window::new("Timer") |  | ||||||
|             .open(&mut self.show_timer) |  | ||||||
|             .show(ctx, |ui| { |  | ||||||
|                 let timer = game_boy.timer(); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("DIV"); |  | ||||||
|                     ui.monospace(format!("{:#06X}", timer.divider)); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("TIMA"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", timer.counter)); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("TMA"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", timer.modulo)); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("TAC"); |  | ||||||
|                     ui.monospace(format!("{:?}", timer.ctrl)); |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         egui::Window::new("Registers") |  | ||||||
|             .open(&mut self.show_registers) |  | ||||||
|             .show(ctx, |ui| { |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("A"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::A))); |  | ||||||
|  |  | ||||||
|                     ui.label("F"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::Flag))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("B"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::B))); |  | ||||||
|  |  | ||||||
|                     ui.label("C"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::C))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("D"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::D))); |  | ||||||
|  |  | ||||||
|                     ui.label("E"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::E))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("H"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::H))); |  | ||||||
|  |  | ||||||
|                     ui.label("L"); |  | ||||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::L))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("AF"); |  | ||||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::AF))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("BC"); |  | ||||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::BC))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("DE"); |  | ||||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::DE))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("HL"); |  | ||||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::HL))); |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         egui::Window::new("Cpu Information") |  | ||||||
|             .open(&mut self.show_cpu_info) |  | ||||||
|             .show(ctx, |ui| { |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("PC"); |  | ||||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::PC))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("SP"); |  | ||||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::SP))); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("IME"); |  | ||||||
|                     ui.label(format!("{:?}", game_boy.ime())); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     ui.label("HALT"); |  | ||||||
|                     ui.label(format!("{:?}", game_boy.halted())); |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         egui::Window::new("IRQ Information") |  | ||||||
|             .open(&mut self.show_int) |  | ||||||
|             .show(ctx, |ui| { |  | ||||||
|                 let req = game_boy.read_byte(0xFF0F); |  | ||||||
|                 let enabled = game_boy.read_byte(0xFFFF); |  | ||||||
|  |  | ||||||
|                 ui.heading("Interrupt Requests"); |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     let _ = ui.selectable_label(req & 0x01 == 0x01, "VBLANK"); |  | ||||||
|                     let _ = ui.selectable_label((req >> 1) & 0x01 == 0x01, "LCD STAT"); |  | ||||||
|                     let _ = ui.selectable_label((req >> 2) & 0x01 == 0x01, "TIMER"); |  | ||||||
|                     let _ = ui.selectable_label((req >> 3) & 0x01 == 0x01, "SERIAL"); |  | ||||||
|                     let _ = ui.selectable_label((req >> 4) & 0x01 == 0x01, "JOYPAD"); |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 ui.heading("Interrupt Enable"); |  | ||||||
|                 ui.horizontal(|ui| { |  | ||||||
|                     let _ = ui.selectable_label(enabled & 0x01 == 0x01, "VBLANK"); |  | ||||||
|                     let _ = ui.selectable_label((enabled >> 1) & 0x01 == 0x01, "LCD STAT"); |  | ||||||
|                     let _ = ui.selectable_label((enabled >> 2) & 0x01 == 0x01, "TIMER"); |  | ||||||
|                     let _ = ui.selectable_label((enabled >> 3) & 0x01 == 0x01, "SERIAL"); |  | ||||||
|                     let _ = ui.selectable_label((enabled >> 4) & 0x01 == 0x01, "JOYPAD"); |  | ||||||
|                 }); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         egui::Window::new("Configuration") |  | ||||||
|             .open(&mut self.config.show) |  | ||||||
|             .show(ctx, |_ui| {}); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Render egui. |  | ||||||
|     pub fn render( |  | ||||||
|         &mut self, |  | ||||||
|         encoder: &mut wgpu::CommandEncoder, |  | ||||||
|         render_target: &wgpu::TextureView, |  | ||||||
|         context: &PixelsContext, |  | ||||||
|     ) { |  | ||||||
|         // Upload all resources to the GPU. |  | ||||||
|         self.render_pass.update_texture( |  | ||||||
|             &context.device, |  | ||||||
|             &context.queue, |  | ||||||
|             &self.platform.context().texture(), |  | ||||||
|         ); |  | ||||||
|         self.render_pass |         self.render_pass | ||||||
|             .update_user_textures(&context.device, &context.queue); |             .add_textures(&self.device, &self.queue, &tdelta) | ||||||
|         self.render_pass.update_buffers( |             .expect("add texture ok"); | ||||||
|             &context.device, |         self.render_pass | ||||||
|             &context.queue, |             .update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor); | ||||||
|             &self.paint_jobs, |  | ||||||
|             &self.screen_desc, |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         // Record all render passes. |         // Record all render passes. | ||||||
|         self.render_pass.execute( |         self.render_pass | ||||||
|             encoder, |             .execute( | ||||||
|             render_target, |                 &mut encoder, | ||||||
|             &self.paint_jobs, |                 &output_view, | ||||||
|             &self.screen_desc, |                 &paint_jobs, | ||||||
|  |                 &screen_descriptor, | ||||||
|  |                 Some(Color::BLACK), | ||||||
|  |             ) | ||||||
|  |             .expect("execute render pass"); | ||||||
|  |  | ||||||
|  |         // Submit the commands. | ||||||
|  |         self.queue.submit(std::iter::once(encoder.finish())); | ||||||
|  |  | ||||||
|  |         // Redraw egui | ||||||
|  |         output_frame.present(); | ||||||
|  |  | ||||||
|  |         self.render_pass | ||||||
|  |             .remove_textures(tdelta) | ||||||
|  |             .expect("remove texture delta"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     pub fn draw_egui(&mut self, cpu: &Cpu, ctx: &Context, texture_id: TextureId) { | ||||||
|  |         use crate::{cpu, instruction, ppu}; | ||||||
|  |  | ||||||
|  |         fn selectable_text(ui: &mut egui::Ui, mut text: &str) -> egui::Response { | ||||||
|  |             ui.add(egui::TextEdit::multiline(&mut text).code_editor()) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { | ||||||
|  |             ui.menu_button("File", |ui| { | ||||||
|  |                 if ui.button("Quit").clicked() { | ||||||
|  |                     self.should_quit = true; | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             egui::Window::new(&self.title).show(ctx, |ui| { | ||||||
|  |                 ui.image( | ||||||
|  |                     texture_id, | ||||||
|  |                     [GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE], | ||||||
|  |                 ); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             egui::Window::new("Disassembly").show(ctx, |ui| { | ||||||
|  |                 selectable_text(ui, &instruction::dbg::tmp_disasm(cpu, 20)); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             egui::Window::new("Settings").show(ctx, |ui| { | ||||||
|  |                 egui::ComboBox::from_label("Emulation Mode") | ||||||
|  |                     .selected_text(format!("{:?}", self.mode)) | ||||||
|  |                     .show_ui(ui, |ui| { | ||||||
|  |                         ui.selectable_value(&mut self.mode, EmuMode::Running, "Running"); | ||||||
|  |                         ui.selectable_value(&mut self.mode, EmuMode::Stopped, "Stopped"); | ||||||
|  |                         ui.selectable_value(&mut self.mode, EmuMode::StepFrame, "Step Frame"); | ||||||
|  |                         ui.selectable_value(&mut self.mode, EmuMode::Step, "Step"); | ||||||
|  |                     }) | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             egui::Window::new("GB Info").show(ctx, |ui| { | ||||||
|  |                 ui.horizontal(|ui| { | ||||||
|  |                     ui.vertical(|ui| { | ||||||
|  |                         ui.heading("CPU"); | ||||||
|  |  | ||||||
|  |                         ui.monospace(format!("AF: {:#06X}", cpu::dbg::af(cpu))); | ||||||
|  |                         ui.monospace(format!("BC: {:#06X}", cpu::dbg::bc(cpu))); | ||||||
|  |                         ui.monospace(format!("DE: {:#06X}", cpu::dbg::de(cpu))); | ||||||
|  |                         ui.monospace(format!("HL: {:#06X}", cpu::dbg::hl(cpu))); | ||||||
|  |                         ui.add_space(10.0); | ||||||
|  |                         ui.monospace(format!("SP: {:#06X}", cpu::dbg::sp(cpu))); | ||||||
|  |                         ui.monospace(format!("PC: {:#06X}", cpu::dbg::pc(cpu))); | ||||||
|  |                     }); | ||||||
|  |  | ||||||
|  |                     ui.vertical(|ui| { | ||||||
|  |                         let ppu = &cpu.bus.ppu; | ||||||
|  |  | ||||||
|  |                         ui.heading("PPU"); | ||||||
|  |  | ||||||
|  |                         ui.monospace(format!("LY: {}", ppu::dbg::ly(ppu))); | ||||||
|  |                         ui.horizontal(|ui| { | ||||||
|  |                             ui.monospace(format!("SCX: {}", ppu::dbg::scx(ppu))); | ||||||
|  |                             ui.monospace(format!("SCY: {}", ppu::dbg::scy(ppu))); | ||||||
|  |                         }); | ||||||
|  |                         ui.horizontal(|ui| { | ||||||
|  |                             ui.monospace(format!("WX: {}", ppu::dbg::wx(ppu))); | ||||||
|  |                             ui.monospace(format!("WY: {}", ppu::dbg::wy(ppu))); | ||||||
|  |                         }); | ||||||
|  |  | ||||||
|  |                         ui.monospace(format!( | ||||||
|  |                             "Mode: {:?} {}", | ||||||
|  |                             ppu::dbg::mode(ppu), | ||||||
|  |                             ppu::dbg::dot(ppu) | ||||||
|  |                         )) | ||||||
|  |                     }); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 ui.add_space(10.0); | ||||||
|  |  | ||||||
|  |                 let _ = ui.selectable_label(cpu::dbg::ime(cpu), "IME"); | ||||||
|  |                 ui.horizontal(|ui| { | ||||||
|  |                     let irq = cpu.int_request(); | ||||||
|  |  | ||||||
|  |                     ui.label("IRQ:"); | ||||||
|  |                     let _ = ui.selectable_label(irq & 0b01 == 0x01, "VBlank"); | ||||||
|  |                     let _ = ui.selectable_label(irq >> 1 & 0x01 == 0x01, "LCD STAT"); | ||||||
|  |                     let _ = ui.selectable_label(irq >> 2 & 0x01 == 0x01, "Timer"); | ||||||
|  |                     let _ = ui.selectable_label(irq >> 3 & 0x01 == 0x01, "Serial"); | ||||||
|  |                     let _ = ui.selectable_label(irq >> 4 & 0x01 == 0x01, "Joypad"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 ui.horizontal(|ui| { | ||||||
|  |                     let ie = cpu.int_enable(); | ||||||
|  |  | ||||||
|  |                     // TODO: Reimplement this | ||||||
|  |                     // let r_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'R'); | ||||||
|  |                     // let e_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'E'); | ||||||
|  |                     // let q_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'Q'); | ||||||
|  |  | ||||||
|  |                     ui.label("IE:"); | ||||||
|  |                     // ui.add_space(q_len - (e_len - r_len)); | ||||||
|  |                     let _ = ui.selectable_label(ie & 0b01 == 0x01, "VBlank"); | ||||||
|  |                     let _ = ui.selectable_label(ie >> 1 & 0x01 == 0x01, "LCD STAT"); | ||||||
|  |                     let _ = ui.selectable_label(ie >> 2 & 0x01 == 0x01, "Timer"); | ||||||
|  |                     let _ = ui.selectable_label(ie >> 3 & 0x01 == 0x01, "Serial"); | ||||||
|  |                     let _ = ui.selectable_label(ie >> 4 & 0x01 == 0x01, "Joypad"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |                 ui.add_space(10.0); | ||||||
|  |  | ||||||
|  |                 ui.horizontal(|ui| { | ||||||
|  |                     let flags = cpu::dbg::flags(cpu); | ||||||
|  |  | ||||||
|  |                     let _ = ui.selectable_label((flags >> 7 & 0x01) == 0x01, "Zero"); | ||||||
|  |                     let _ = ui.selectable_label((flags >> 6 & 0x01) == 0x01, "Negative"); | ||||||
|  |                     let _ = ui.selectable_label((flags >> 5 & 0x01) == 0x01, "Half-Carry"); | ||||||
|  |                     let _ = ui.selectable_label((flags >> 4 & 0x01) == 0x01, "Carry"); | ||||||
|  |                 }) | ||||||
|  |             }) | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
|  | pub enum EmuMode { | ||||||
|  |     Running, | ||||||
|  |     StepFrame, | ||||||
|  |     Stopped, | ||||||
|  |     Step, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// To avoid using an [Option<KeyboardInput>] to keep track of user input from winit, | ||||||
|  | /// we can use a "default" value. However, in order for this to work the chosen "default" | ||||||
|  | /// value must be an **unused** key, so that it is ignored by the emulator. | ||||||
|  | pub fn unused_key() -> KeyboardInput { | ||||||
|  |     #![allow(deprecated)] | ||||||
|  |  | ||||||
|  |     KeyboardInput { | ||||||
|  |         scancode: Default::default(), | ||||||
|  |         state: ElementState::Released, | ||||||
|  |         virtual_keycode: Default::default(), | ||||||
|  |         modifiers: Default::default(), // this argument is deprecated | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> { | ||||||
|  |     use winit::window::WindowBuilder; | ||||||
|  |  | ||||||
|  |     WindowBuilder::new() | ||||||
|  |         .with_decorations(true) | ||||||
|  |         .with_resizable(true) | ||||||
|  |         .with_transparent(false) | ||||||
|  |         .with_title(WINDOW_TITLE) | ||||||
|  |         .with_inner_size(PhysicalSize { | ||||||
|  |             width: EGUI_DIMENSIONS.0 as f32, | ||||||
|  |             height: EGUI_DIMENSIONS.1 as f32, | ||||||
|  |         }) | ||||||
|  |         .build(event_loop) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> { | ||||||
|  |     use wgpu::{PowerPreference, RequestAdapterOptions}; | ||||||
|  |  | ||||||
|  |     pollster::block_on(instance.request_adapter(&RequestAdapterOptions { | ||||||
|  |         power_preference: PowerPreference::HighPerformance, | ||||||
|  |         compatible_surface: Some(surface), | ||||||
|  |         force_fallback_adapter: false, // TODO: What do I want to do with this? | ||||||
|  |     })) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> { | ||||||
|  |     use wgpu::{DeviceDescriptor, Features, Limits}; | ||||||
|  |  | ||||||
|  |     pollster::block_on(adapter.request_device( | ||||||
|  |         &DeviceDescriptor { | ||||||
|  |             features: Features::default(), | ||||||
|  |             limits: Limits::default(), | ||||||
|  |             label: None, | ||||||
|  |         }, | ||||||
|         None, |         None, | ||||||
|  |     )) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn surface_config(window: &Window, capabilities: SurfaceCapabilities) -> SurfaceConfiguration { | ||||||
|  |     use egui_wgpu_backend::wgpu::{PresentMode, TextureFormat, TextureUsages}; | ||||||
|  |  | ||||||
|  |     let size = window.inner_size(); | ||||||
|  |     SurfaceConfiguration { | ||||||
|  |         usage: TextureUsages::RENDER_ATTACHMENT, | ||||||
|  |         format: capabilities.formats[0], | ||||||
|  |         width: size.width as u32, | ||||||
|  |         height: size.height as u32, | ||||||
|  |         present_mode: PresentMode::Fifo, | ||||||
|  |         alpha_mode: capabilities.alpha_modes[0], | ||||||
|  |         view_formats: vec![TextureFormat::Rgba8UnormSrgb], | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn platform(window: &Window) -> Platform { | ||||||
|  |     use egui::FontDefinitions; | ||||||
|  |     use egui_winit_platform::PlatformDescriptor; | ||||||
|  |  | ||||||
|  |     let size = window.inner_size(); | ||||||
|  |     Platform::new(PlatformDescriptor { | ||||||
|  |         physical_width: size.width as u32, | ||||||
|  |         physical_height: size.height as u32, | ||||||
|  |         scale_factor: window.scale_factor(), | ||||||
|  |         font_definitions: FontDefinitions::default(), | ||||||
|  |         ..Default::default() | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn texture_size() -> Extent3d { | ||||||
|  |     Extent3d { | ||||||
|  |         width: GB_WIDTH as u32, | ||||||
|  |         height: GB_HEIGHT as u32, | ||||||
|  |         ..Default::default() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn create_texture(device: &Device, size: Extent3d) -> Texture { | ||||||
|  |     use wgpu::{TextureDescriptor, TextureDimension, TextureFormat, TextureUsages}; | ||||||
|  |  | ||||||
|  |     device.create_texture(&TextureDescriptor { | ||||||
|  |         size, | ||||||
|  |         mip_level_count: 1, | ||||||
|  |         sample_count: 1, | ||||||
|  |         dimension: TextureDimension::D2, | ||||||
|  |         format: TextureFormat::Bgra8Unorm, | ||||||
|  |         usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, | ||||||
|  |         label: Some("gb_pixel_buffer"), | ||||||
|  |         view_formats: &[TextureFormat::Bgra8Unorm], | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[inline] | ||||||
|  | fn write_to_texture( | ||||||
|  |     queue: &Queue, | ||||||
|  |     texture: &Texture, | ||||||
|  |     data: &[u8; GB_WIDTH * 4 * GB_HEIGHT], | ||||||
|  |     size: Extent3d, | ||||||
|  | ) { | ||||||
|  |     use std::num::NonZeroU32; | ||||||
|  |     use wgpu::{ImageCopyTexture, ImageDataLayout, Origin3d, TextureAspect}; | ||||||
|  |  | ||||||
|  |     queue.write_texture( | ||||||
|  |         ImageCopyTexture { | ||||||
|  |             texture, | ||||||
|  |             mip_level: 0, | ||||||
|  |             origin: Origin3d::ZERO, | ||||||
|  |             aspect: TextureAspect::All, | ||||||
|  |         }, | ||||||
|  |         data, | ||||||
|  |         ImageDataLayout { | ||||||
|  |             offset: 0, | ||||||
|  |             bytes_per_row: NonZeroU32::new(4 * GB_WIDTH as u32), | ||||||
|  |             rows_per_image: NonZeroU32::new(GB_HEIGHT as u32), | ||||||
|  |         }, | ||||||
|  |         size, | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn expose_texture_to_egui( | ||||||
|  |     render_pass: &mut RenderPass, | ||||||
|  |     device: &Device, | ||||||
|  |     view: &TextureView, | ||||||
|  | ) -> TextureId { | ||||||
|  |     render_pass.egui_texture_from_wgpu_texture(device, view, FILTER_MODE) | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct Configuration { | #[inline] | ||||||
|     /// Show Configuration egui menu | fn create_command_encoder(device: &Device) -> CommandEncoder { | ||||||
|     show: bool, |     use wgpu::CommandEncoderDescriptor; | ||||||
|  |  | ||||||
|  |     device.create_command_encoder(&CommandEncoderDescriptor { | ||||||
|  |         label: Some("encoder"), | ||||||
|  |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Configuration { | #[inline] | ||||||
|     fn default() -> Self { | fn create_screen_descriptor(window: &Window, config: &SurfaceConfiguration) -> ScreenDescriptor { | ||||||
|         Self { show: false } |     ScreenDescriptor { | ||||||
|  |         physical_width: config.width, | ||||||
|  |         physical_height: config.height, | ||||||
|  |         scale_factor: window.scale_factor() as f32, | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub mod kbd { | ||||||
|  |     use winit::event::{ElementState, KeyboardInput, VirtualKeyCode}; | ||||||
|  |  | ||||||
|  |     pub fn space_released(input: &KeyboardInput) -> bool { | ||||||
|  |         let keycode = input.virtual_keycode; | ||||||
|  |         matches!(input.state, ElementState::Released if keycode == Some(VirtualKeyCode::Space)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,11 +17,11 @@ impl Default for HighRam { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl BusIo for HighRam { | impl BusIo for HighRam { | ||||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { |  | ||||||
|         self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn read_byte(&self, addr: u16) -> u8 { |     fn read_byte(&self, addr: u16) -> u8 { | ||||||
|         self.buf[addr as usize - HIGH_RAM_START_ADDRESS] |         self.buf[addr as usize - HIGH_RAM_START_ADDRESS] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||||
|  |         self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										3171
									
								
								src/instruction.rs
									
									
									
									
									
								
							
							
						
						
									
										3171
									
								
								src/instruction.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,11 +1,20 @@ | |||||||
| use bitfield::bitfield; | use bitfield::bitfield; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug)] | ||||||
| pub(crate) struct Interrupt { | pub(crate) struct Interrupt { | ||||||
|     pub(crate) flag: InterruptFlag, |     pub(crate) flag: InterruptFlag, | ||||||
|     pub(crate) enable: InterruptEnable, |     pub(crate) enable: InterruptEnable, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl Default for Interrupt { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             flag: InterruptFlag(0), | ||||||
|  |             enable: InterruptEnable(0), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| bitfield! { | bitfield! { | ||||||
|    pub struct InterruptEnable(u8); |    pub struct InterruptEnable(u8); | ||||||
|     impl Debug; |     impl Debug; | ||||||
| @@ -23,12 +32,6 @@ impl Clone for InterruptEnable { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for InterruptEnable { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for InterruptEnable { | impl From<u8> for InterruptEnable { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         Self(byte) |         Self(byte) | ||||||
| @@ -58,12 +61,6 @@ impl Clone for InterruptFlag { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for InterruptFlag { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for InterruptFlag { | impl From<u8> for InterruptFlag { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         Self(byte) |         Self(byte) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType}; | use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType}; | ||||||
| use winit_input_helper::WinitInputHelper; | use winit::event::{ElementState, KeyboardInput, VirtualKeyCode}; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug)] | ||||||
| pub struct Joypad { | pub struct Joypad { | ||||||
|     /// 0xFF00 | P1/JOYP - Player 1 Joypad |     /// 0xFF00 | P1/JOYP - Player 1 Joypad | ||||||
|     pub(crate) p1: u8, |     pub(crate) p1: u8, | ||||||
| @@ -48,7 +48,7 @@ impl Joypad { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Default)] | ||||||
| struct JoypadState { | struct JoypadState { | ||||||
|     // Direction Row |     // Direction Row | ||||||
|     dpad_down: ButtonEvent, |     dpad_down: ButtonEvent, | ||||||
| @@ -110,64 +110,39 @@ impl ButtonEvent { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) { | #[inline] | ||||||
|     use winit::event::VirtualKeyCode; | pub(crate) fn handle_keyboard_input(pad: &mut Joypad, key: KeyboardInput) { | ||||||
|  |  | ||||||
|     // TODO: What do I have to do to get a match statement here? |  | ||||||
|  |  | ||||||
|     let state = &mut pad.ext; |     let state = &mut pad.ext; | ||||||
|     let irq = &mut pad.interrupt; |     let irq = &mut pad.interrupt; | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Down) { |     match key.state { | ||||||
|         state.dpad_down.update(true, irq); |         ElementState::Pressed => match key.virtual_keycode { | ||||||
|     } else if input.key_released(VirtualKeyCode::Down) { |             Some(VirtualKeyCode::Down) => state.dpad_down.update(true, irq), | ||||||
|         state.dpad_down.update(false, irq); |             Some(VirtualKeyCode::Up) => state.dpad_up.update(true, irq), | ||||||
|     } |             Some(VirtualKeyCode::Left) => state.dpad_left.update(true, irq), | ||||||
|  |             Some(VirtualKeyCode::Right) => state.dpad_right.update(true, irq), | ||||||
|     if input.key_pressed(VirtualKeyCode::Up) { |             Some(VirtualKeyCode::Return) => state.start.update(true, irq), | ||||||
|         state.dpad_up.update(true, irq); |             Some(VirtualKeyCode::RShift) => state.select.update(true, irq), | ||||||
|     } else if input.key_released(VirtualKeyCode::Up) { |             Some(VirtualKeyCode::Z) => state.south.update(true, irq), | ||||||
|         state.dpad_up.update(false, irq); |             Some(VirtualKeyCode::X) => state.east.update(true, irq), | ||||||
|     } |             None | Some(_) => {} | ||||||
|  |         }, | ||||||
|     if input.key_pressed(VirtualKeyCode::Left) { |         ElementState::Released => match key.virtual_keycode { | ||||||
|         state.dpad_left.update(true, irq); |             Some(VirtualKeyCode::Down) => state.dpad_down.update(false, irq), | ||||||
|     } else if input.key_released(VirtualKeyCode::Left) { |             Some(VirtualKeyCode::Up) => state.dpad_up.update(false, irq), | ||||||
|         state.dpad_left.update(false, irq); |             Some(VirtualKeyCode::Left) => state.dpad_left.update(false, irq), | ||||||
|     } |             Some(VirtualKeyCode::Right) => state.dpad_right.update(false, irq), | ||||||
|  |             Some(VirtualKeyCode::Return) => state.start.update(false, irq), | ||||||
|     if input.key_pressed(VirtualKeyCode::Right) { |             Some(VirtualKeyCode::RShift) => state.select.update(false, irq), | ||||||
|         state.dpad_right.update(true, irq); |             Some(VirtualKeyCode::Z) => state.south.update(false, irq), | ||||||
|     } else if input.key_released(VirtualKeyCode::Right) { |             Some(VirtualKeyCode::X) => state.east.update(false, irq), | ||||||
|         state.dpad_right.update(false, irq); |             None | Some(_) => {} | ||||||
|     } |         }, | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::T) { |  | ||||||
|         state.start.update(true, irq); |  | ||||||
|     } else if input.key_released(VirtualKeyCode::T) { |  | ||||||
|         state.start.update(false, irq); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Y) { |  | ||||||
|         state.select.update(true, irq); |  | ||||||
|     } else if input.key_released(VirtualKeyCode::Y) { |  | ||||||
|         state.select.update(false, irq); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::Z) { |  | ||||||
|         state.south.update(true, irq); |  | ||||||
|     } else if input.key_released(VirtualKeyCode::Z) { |  | ||||||
|         state.south.update(false, irq); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if input.key_pressed(VirtualKeyCode::X) { |  | ||||||
|         state.east.update(true, irq); |  | ||||||
|     } else if input.key_released(VirtualKeyCode::X) { |  | ||||||
|         state.east.update(false, irq); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| pub fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) { | #[inline] | ||||||
|  | pub(crate) fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) { | ||||||
|     use Button::*; |     use Button::*; | ||||||
|     use GamepadEventType::*; |     use GamepadEventType::*; | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/lib.rs
									
									
									
									
									
								
							| @@ -1,21 +1,22 @@ | |||||||
| pub use gui::Egui; | #![allow(clippy::derivable_impls)] // Will remove this if bitfield-rs allows default impls | ||||||
| pub use instruction::Cycle; |  | ||||||
| pub use sound::AudioMPSC; | pub use apu::gen::init as spsc_init; | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  | mod apu; | ||||||
| mod bus; | mod bus; | ||||||
| mod cartridge; | mod cartridge; | ||||||
| mod cpu; | mod cpu; | ||||||
| pub mod emu; | pub mod emu; | ||||||
| mod gui; | pub mod gui; | ||||||
| mod high_ram; | mod high_ram; | ||||||
| mod instruction; | mod instruction; | ||||||
| mod interrupt; | mod interrupt; | ||||||
| mod joypad; | mod joypad; | ||||||
| mod ppu; | mod ppu; | ||||||
| mod serial; | mod serial; | ||||||
| mod sound; |  | ||||||
| mod timer; | mod timer; | ||||||
| mod work_ram; | mod work_ram; | ||||||
|   | |||||||
							
								
								
									
										212
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										212
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,154 +1,126 @@ | |||||||
| use anyhow::{anyhow, Result}; | use std::path::PathBuf; | ||||||
| use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; |  | ||||||
| use gb::{AudioMPSC, Cycle, Egui, GB_HEIGHT, GB_WIDTH}; |  | ||||||
| use gilrs::Gilrs; |  | ||||||
| use pixels::{Pixels, SurfaceTexture}; |  | ||||||
| use rodio::{OutputStream, Sink}; |  | ||||||
| use std::time::Instant; | use std::time::Instant; | ||||||
| use winit::dpi::LogicalSize; |  | ||||||
| use winit::event::{Event, VirtualKeyCode}; |  | ||||||
| use winit::event_loop::{ControlFlow, EventLoop}; |  | ||||||
| use winit::window::{Window, WindowBuilder}; |  | ||||||
| use winit_input_helper::WinitInputHelper; |  | ||||||
|  |  | ||||||
| const SCALE: f64 = 2.0; | use clap::{arg, command, value_parser}; | ||||||
|  | use gb::gui::{EmuMode, Gui}; | ||||||
|  | use gb::{emu, gui}; | ||||||
|  | use gilrs::Gilrs; | ||||||
|  | use rodio::{OutputStream, Sink}; | ||||||
|  | use tracing_subscriber::EnvFilter; | ||||||
|  | use winit::event::{Event, WindowEvent}; | ||||||
|  | use winit::event_loop::{EventLoop, EventLoopBuilder}; | ||||||
|  |  | ||||||
| fn main() -> Result<()> { | const AUDIO_ENABLED: bool = true; | ||||||
|     let app = App::new(crate_name!()) |  | ||||||
|         .version(crate_version!()) |  | ||||||
|         .author(crate_authors!()) |  | ||||||
|         .about(crate_description!()); |  | ||||||
|  |  | ||||||
|     let m = app | fn main() { | ||||||
|  |     let m = command!() | ||||||
|         .arg( |         .arg( | ||||||
|             Arg::with_name("rom") |             arg!(-b --boot <FILE> "path to boot ROM") | ||||||
|                 .value_name("ROM_FILE") |                 .required(false) | ||||||
|                 .takes_value(true) |                 .value_parser(value_parser!(PathBuf)), | ||||||
|                 .required(true) |  | ||||||
|                 .index(1) |  | ||||||
|                 .help("Path to the Game ROM"), |  | ||||||
|         ) |         ) | ||||||
|         .arg( |         .arg( | ||||||
|             Arg::with_name("boot") |             arg!([ROM_FILE] "path to game ROM") | ||||||
|                 .short("b") |                 .required(true) | ||||||
|                 .long("boot") |                 .value_parser(value_parser!(PathBuf)), | ||||||
|                 .value_name("FILE") |  | ||||||
|                 .takes_value(true) |  | ||||||
|                 .help("Path to Boot ROM"), |  | ||||||
|         ) |         ) | ||||||
|         .get_matches(); |         .get_matches(); | ||||||
|  |  | ||||||
|     // `rom` is a required value in every situation so this will |     // Set up subscriber | ||||||
|     // always exist. |     if std::env::var("RUST_LOG").is_err() { | ||||||
|     let rom_path = m |         std::env::set_var("RUST_LOG", "gb=info"); | ||||||
|         .value_of("rom") |     } | ||||||
|         .expect("Required value 'rom' was provided"); |  | ||||||
|  |  | ||||||
|     let mut game_boy = |     tracing_subscriber::fmt::fmt() | ||||||
|         gb::emu::init(m.value_of("boot"), rom_path).expect("Initialized DMG-01 Emulator"); |         .with_env_filter(EnvFilter::from_default_env()) | ||||||
|     let cartridge_title = gb::emu::rom_title(&game_boy); |         .init(); | ||||||
|  |  | ||||||
|     // Initialize Gamepad Support |     // Init CPU | ||||||
|     let mut gamepad = Gilrs::new().expect("Initialized Gilrs for Controller Input"); |     let mut cpu = match m.get_one::<PathBuf>("boot") { | ||||||
|  |         Some(path) => { | ||||||
|     // Initialize GUI |             tracing::info!("User-provided boot ROM"); | ||||||
|     let event_loop = EventLoop::new(); |             emu::from_boot_rom(path).expect("initialize emulator with custom boot rom") | ||||||
|     let mut input = WinitInputHelper::new(); |         } | ||||||
|     let window = create_window(&event_loop, cartridge_title)?; |         None => { | ||||||
|  |             tracing::info!("Built-in boot ROM"); | ||||||
|     let (mut pixels, mut egui) = { |             Default::default() | ||||||
|         let size = window.inner_size(); |         } | ||||||
|         let scale_factor = window.scale_factor(); |  | ||||||
|         let surface_texture = SurfaceTexture::new(size.width, size.height, &window); |  | ||||||
|         let pixels = Pixels::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)?; |  | ||||||
|         let egui = Egui::new(size.width, size.height, scale_factor, pixels.context()); |  | ||||||
|  |  | ||||||
|         (pixels, egui) |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let (send, recv) = AudioMPSC::new(); |     // Load ROM if filepath was provided | ||||||
|     game_boy.set_audio_src(send); |     if let Some(path) = m.get_one::<PathBuf>("ROM_FILE") { | ||||||
|  |         tracing::info!("User-provided cartridge ROM"); | ||||||
|  |         emu::read_game_rom(&mut cpu, path).expect("read game rom from path"); | ||||||
|  |     } | ||||||
|  |     emu::load_save(&mut cpu); | ||||||
|  |     let rom_title = emu::rom_title(&cpu).to_string(); | ||||||
|  |  | ||||||
|  |     tracing::info!("Initialize Gamepad"); | ||||||
|  |     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); | ||||||
|  |  | ||||||
|     // Initialize Audio |     // Initialize Audio | ||||||
|     let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio"); |     let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio"); | ||||||
|     let sink = Sink::try_new(&stream_handle).expect("Initialize Audio Sink"); |  | ||||||
|  |  | ||||||
|  |     if AUDIO_ENABLED { | ||||||
|  |         let (prod, cons) = gb::spsc_init(); | ||||||
|  |         let sink = { | ||||||
|  |             let s = Sink::try_new(&stream_handle).expect("create sink from audio stream handle"); | ||||||
|  |             s.append(cons); | ||||||
|  |             s.set_volume(0.1); | ||||||
|  |             s | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         emu::set_audio_prod(&mut cpu, prod); | ||||||
|  |  | ||||||
|  |         tracing::info!("Spawn Audio Thread"); | ||||||
|         std::thread::spawn(move || { |         std::thread::spawn(move || { | ||||||
|         sink.append(recv); |  | ||||||
|  |  | ||||||
|             sink.sleep_until_end(); |             sink.sleep_until_end(); | ||||||
|         }); |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     let mut now = Instant::now(); |     // Set up state for the Immediate-mode GUI | ||||||
|     let mut cycle_count: Cycle = Default::default(); |     let event_loop: EventLoop<Event<()>> = EventLoopBuilder::with_user_event().build(); | ||||||
|  |  | ||||||
|  |     let mut app = Gui::new(rom_title, &event_loop, &cpu); | ||||||
|  |     let mut last_key = gui::unused_key(); // TODO: Fix this awful impl | ||||||
|  |  | ||||||
|  |     // used for egui animations | ||||||
|  |     let start_time = Instant::now(); | ||||||
|  |  | ||||||
|     event_loop.run(move |event, _, control_flow| { |     event_loop.run(move |event, _, control_flow| { | ||||||
|         // Update egui |         app.handle_event(&event); | ||||||
|         egui.handle_event(&event); |  | ||||||
|  |  | ||||||
|         if let Event::RedrawRequested(_) = event { |         match event { | ||||||
|             // Prepare egui |             Event::MainEventsCleared => { | ||||||
|             egui.prepare(&game_boy); |                 app.maybe_quit(&cpu, control_flow); | ||||||
|  |  | ||||||
|             // Render everything together |                 match app.mode { | ||||||
|             let render_result = pixels.render_with(|encoder, target, ctx| { |                     EmuMode::Running => emu::run_frame(&mut cpu, &mut gamepad, last_key), | ||||||
|                 // Render the texture |                     EmuMode::StepFrame if gui::kbd::space_released(&last_key) => { | ||||||
|                 ctx.scaling_renderer.render(encoder, target); |                         emu::run_frame(&mut cpu, &mut gamepad, last_key) | ||||||
|  |  | ||||||
|                 // Render egui |  | ||||||
|                 egui.render(encoder, target, ctx); |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|             if render_result |  | ||||||
|                 .map_err(|e| anyhow!("pixels.render() failed: {}", e)) |  | ||||||
|                 .is_err() |  | ||||||
|             { |  | ||||||
|                 *control_flow = ControlFlow::Exit; |  | ||||||
|                 return; |  | ||||||
|                     } |                     } | ||||||
|  |                     EmuMode::Step if gui::kbd::space_released(&last_key) => { | ||||||
|  |                         emu::run(&mut cpu, &mut gamepad, last_key, 4); | ||||||
|                     } |                     } | ||||||
|  |                     _ => {} | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|         if input.update(&event) { |                 // Input has been consumed, reset it | ||||||
|             if input.key_pressed(VirtualKeyCode::Escape) || input.quit() { |                 last_key = gui::unused_key(); | ||||||
|                 *control_flow = ControlFlow::Exit; |  | ||||||
|                 return; |                 app.request_redraw(); | ||||||
|             } |             } | ||||||
|  |             Event::RedrawRequested(..) => { | ||||||
|             if let Some(scale_factor) = input.scale_factor() { |                 app.update_time(start_time.elapsed().as_secs_f64()); | ||||||
|                 egui.scale_factor(scale_factor); |                 app.paint(&cpu); | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if let Some(size) = input.window_resized() { |  | ||||||
|                 pixels.resize_surface(size.width, size.height); |  | ||||||
|                 egui.resize(size.width, size.height); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             let delta = now.elapsed().subsec_nanos(); |  | ||||||
|             now = Instant::now(); |  | ||||||
|  |  | ||||||
|             let pending = Cycle::new(delta / gb::emu::SM83_CYCLE_TIME.subsec_nanos()); |  | ||||||
|             cycle_count += gb::emu::run(&mut game_boy, &mut gamepad, &input, pending); |  | ||||||
|  |  | ||||||
|             if cycle_count >= gb::emu::CYCLES_IN_FRAME { |  | ||||||
|                 // Draw Frame |  | ||||||
|                 cycle_count = Cycle::new(0); |  | ||||||
|  |  | ||||||
|                 gb::emu::draw(game_boy.ppu(), pixels.get_frame()); |  | ||||||
|                 window.request_redraw(); |  | ||||||
|             } |             } | ||||||
|  |             Event::WindowEvent { event, .. } => match event { | ||||||
|  |                 WindowEvent::Resized(size) => app.resize(size), | ||||||
|  |                 WindowEvent::CloseRequested => emu::save_and_exit(&cpu, control_flow), | ||||||
|  |                 WindowEvent::KeyboardInput { input, .. } => last_key = input, | ||||||
|  |                 _ => {} | ||||||
|  |             }, | ||||||
|  |             _ => {} | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { |  | ||||||
|     let size = LogicalSize::new((GB_WIDTH as f64) * SCALE, (GB_HEIGHT as f64) * 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) // OleInitialize failed error if this is set to true |  | ||||||
|         .build(event_loop)?) |  | ||||||
| } |  | ||||||
|   | |||||||
							
								
								
									
										854
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										854
									
								
								src/ppu.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| use crate::instruction::Cycle; | use crate::Cycle; | ||||||
|  |  | ||||||
| #[derive(Debug, Default, Clone)] | #[derive(Debug)] | ||||||
| pub(crate) struct DirectMemoryAccess { | pub(crate) struct DirectMemoryAccess { | ||||||
|     pub(crate) state: DmaState, |     pub(crate) state: DmaState, | ||||||
|     cycle: Cycle, |     cycle: Cycle, | ||||||
| @@ -8,8 +8,18 @@ pub(crate) struct DirectMemoryAccess { | |||||||
|     pub(crate) start: DmaAddress, |     pub(crate) start: DmaAddress, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl Default for DirectMemoryAccess { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             state: DmaState::Disabled, | ||||||
|  |             cycle: Default::default(), | ||||||
|  |             start: Default::default(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl DirectMemoryAccess { | impl DirectMemoryAccess { | ||||||
|     pub(crate) fn clock(&mut self) -> Option<(u16, u16)> { |     pub(crate) fn tick(&mut self) -> Option<(u16, u16)> { | ||||||
|         match self.state { |         match self.state { | ||||||
|             DmaState::Pending => { |             DmaState::Pending => { | ||||||
|                 self.cycle += 1; |                 self.cycle += 1; | ||||||
| @@ -28,7 +38,7 @@ impl DirectMemoryAccess { | |||||||
|  |  | ||||||
|                 let src_addr = self |                 let src_addr = self | ||||||
|                     .start |                     .start | ||||||
|                     .addr |                     .0 | ||||||
|                     .as_mut() |                     .as_mut() | ||||||
|                     .expect("Source Address present during DMA Transfer"); |                     .expect("Source Address present during DMA Transfer"); | ||||||
|  |  | ||||||
| @@ -56,9 +66,9 @@ 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.addr = None; |         self.start.0 = None; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -69,32 +79,21 @@ pub(crate) enum DmaState { | |||||||
|     Transferring, |     Transferring, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for DmaState { | #[derive(Debug, Clone, Copy, Default)] | ||||||
|     fn default() -> Self { | pub(crate) struct DmaAddress(Option<u16>); | ||||||
|         Self::Disabled |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Default, Clone, Copy)] |  | ||||||
| pub(crate) struct DmaAddress { |  | ||||||
|     /// The current *source* address of the DMA Transfer |  | ||||||
|     /// |  | ||||||
|     /// NB: Will be None if no DMA Transfer is in progress |  | ||||||
|     addr: Option<u16>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl DmaAddress { | impl DmaAddress { | ||||||
|     pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) { |     pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) { | ||||||
|         let start = (byte as u16) << 8; |         let start = (byte as u16) << 8; | ||||||
|  |  | ||||||
|         self.addr = Some(start); |         self.0 = Some(start); | ||||||
|         *state = DmaState::Pending; |         *state = DmaState::Pending; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<DmaAddress> for u8 { | impl From<DmaAddress> for u8 { | ||||||
|     fn from(ctrl: DmaAddress) -> Self { |     fn from(ctrl: DmaAddress) -> Self { | ||||||
|         match ctrl.addr { |         match ctrl.0 { | ||||||
|             Some(addr) => (addr >> 8) as u8, |             Some(addr) => (addr >> 8) as u8, | ||||||
|             None => 0xFF, // TODO: What garbage value should be here? |             None => 0xFF, // TODO: What garbage value should be here? | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -29,12 +29,6 @@ impl Clone for LCDStatus { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for LCDStatus { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0x80) // bit 7 is always 1 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<LCDStatus> for u8 { | impl From<LCDStatus> for u8 { | ||||||
|     fn from(status: LCDStatus) -> Self { |     fn from(status: LCDStatus) -> Self { | ||||||
|         status.0 |         status.0 | ||||||
| @@ -67,12 +61,6 @@ impl From<PpuMode> for u8 { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for PpuMode { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::HBlank |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bitfield! { | bitfield! { | ||||||
|     pub struct LCDControl(u8); |     pub struct LCDControl(u8); | ||||||
|     impl Debug; |     impl Debug; | ||||||
| @@ -93,12 +81,6 @@ impl Clone for LCDControl { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for LCDControl { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for LCDControl { | impl From<u8> for LCDControl { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         Self(byte) |         Self(byte) | ||||||
| @@ -142,12 +124,6 @@ impl From<TileMapAddress> for u8 { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for TileMapAddress { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::X9800 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub enum TileDataAddress { | pub enum TileDataAddress { | ||||||
|     X8800 = 0, |     X8800 = 0, | ||||||
| @@ -170,12 +146,6 @@ impl From<TileDataAddress> for u8 { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for TileDataAddress { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::X8800 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub enum ObjectSize { | pub enum ObjectSize { | ||||||
|     Eight = 0, |     Eight = 0, | ||||||
| @@ -183,7 +153,7 @@ pub enum ObjectSize { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl ObjectSize { | impl ObjectSize { | ||||||
|     pub(crate) fn as_u8(&self) -> u8 { |     pub(crate) fn size(&self) -> u8 { | ||||||
|         use ObjectSize::*; |         use ObjectSize::*; | ||||||
|  |  | ||||||
|         match self { |         match self { | ||||||
| @@ -209,12 +179,6 @@ impl From<ObjectSize> for u8 { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for ObjectSize { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::Eight |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| bitfield! { | bitfield! { | ||||||
|     pub struct BackgroundPalette(u8); |     pub struct BackgroundPalette(u8); | ||||||
|     impl Debug; |     impl Debug; | ||||||
| @@ -243,12 +207,6 @@ impl Clone for BackgroundPalette { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for BackgroundPalette { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for BackgroundPalette { | impl From<u8> for BackgroundPalette { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         Self(byte) |         Self(byte) | ||||||
| @@ -288,12 +246,6 @@ impl Clone for ObjectPalette { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for ObjectPalette { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for ObjectPalette { | impl From<u8> for ObjectPalette { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         Self(byte) |         Self(byte) | ||||||
| @@ -332,7 +284,7 @@ bitfield! { | |||||||
|     pub from into RenderPriority, priority, set_priority: 7, 7; |     pub from into RenderPriority, priority, set_priority: 7, 7; | ||||||
|     pub y_flip, set_y_flip: 6; |     pub y_flip, set_y_flip: 6; | ||||||
|     pub x_flip, set_x_flip: 5; |     pub x_flip, set_x_flip: 5; | ||||||
|     pub from into ObjectPaletteId, palette, set_palette: 4, 4; |     pub from into ObjectPaletteKind, palette, set_palette: 4, 4; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Eq for ObjectFlags {} | impl Eq for ObjectFlags {} | ||||||
| @@ -361,35 +313,29 @@ impl From<ObjectFlags> for u8 { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for ObjectFlags { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub enum ObjectPaletteId { | pub enum ObjectPaletteKind { | ||||||
|     Zero = 0, |     Zero = 0, | ||||||
|     One = 1, |     One = 1, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<u8> for ObjectPaletteId { | impl From<u8> for ObjectPaletteKind { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         match byte & 0b01 { |         match byte & 0b01 { | ||||||
|             0b00 => ObjectPaletteId::Zero, |             0b00 => ObjectPaletteKind::Zero, | ||||||
|             0b01 => ObjectPaletteId::One, |             0b01 => ObjectPaletteKind::One, | ||||||
|             _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte), |             _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<ObjectPaletteId> for u8 { | impl From<ObjectPaletteKind> for u8 { | ||||||
|     fn from(palette_num: ObjectPaletteId) -> Self { |     fn from(palette_num: ObjectPaletteKind) -> Self { | ||||||
|         palette_num as u8 |         palette_num as u8 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, PartialEq)] | #[derive(Debug, Clone, Copy, Eq, PartialEq)] | ||||||
| pub enum RenderPriority { | pub enum RenderPriority { | ||||||
|     Object = 0, |     Object = 0, | ||||||
|     BackgroundAndWindow = 1, |     BackgroundAndWindow = 1, | ||||||
| @@ -411,12 +357,6 @@ impl From<RenderPriority> for u8 { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for RenderPriority { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::Object |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub enum GrayShade { | pub enum GrayShade { | ||||||
|     White = 0, |     White = 0, | ||||||
| @@ -436,12 +376,6 @@ impl GrayShade { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for GrayShade { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::White |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for GrayShade { | impl From<u8> for GrayShade { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         match byte & 0b11 { |         match byte & 0b11 { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| use bitfield::bitfield; | use bitfield::bitfield; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug)] | ||||||
| pub(crate) struct Serial { | pub(crate) struct Serial { | ||||||
|     /// 0xFF01 | SB - Serial Transfer Data |     /// 0xFF01 | SB - Serial Transfer Data | ||||||
|     pub(crate) next: u8, |     pub(crate) next: u8, | ||||||
| @@ -8,6 +8,15 @@ pub(crate) struct Serial { | |||||||
|     pub(crate) ctrl: SerialControl, |     pub(crate) ctrl: SerialControl, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl Default for Serial { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             next: Default::default(), | ||||||
|  |             ctrl: SerialControl(0), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| bitfield! { | bitfield! { | ||||||
|     pub struct SerialControl(u8); |     pub struct SerialControl(u8); | ||||||
|     impl Debug; |     impl Debug; | ||||||
| @@ -23,12 +32,6 @@ impl Clone for SerialControl { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for SerialControl { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for SerialControl { | impl From<u8> for SerialControl { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         Self(byte) |         Self(byte) | ||||||
| @@ -47,12 +50,6 @@ enum ShiftClock { | |||||||
|     Internal = 1, |     Internal = 1, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for ShiftClock { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::External |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for ShiftClock { | impl From<u8> for ShiftClock { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         match byte & 0b01 { |         match byte & 0b01 { | ||||||
| @@ -69,12 +66,6 @@ enum ClockSpeed { | |||||||
|     Fast = 1, |     Fast = 1, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for ClockSpeed { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self::Normal |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for ClockSpeed { | impl From<u8> for ClockSpeed { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         match byte & 0b01 { |         match byte & 0b01 { | ||||||
|   | |||||||
							
								
								
									
										1235
									
								
								src/sound.rs
									
									
									
									
									
								
							
							
						
						
									
										1235
									
								
								src/sound.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										96
									
								
								src/timer.rs
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								src/timer.rs
									
									
									
									
									
								
							| @@ -1,21 +1,24 @@ | |||||||
| use bitfield::bitfield; | use bitfield::bitfield; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug)] | ||||||
| pub(crate) struct Timer { | pub(crate) struct Timer { | ||||||
|     /// 0xFF07 | TAC - Timer Control |     /// 0xFF07 | TAC - Timer Control | ||||||
|     pub(crate) ctrl: TimerControl, |     pub(crate) ctrl: TimerControl, | ||||||
|     /// 0xFF05 | TIMA - Timer Counter |     /// 0xFF05 | TIMA - Timer Counter | ||||||
|     pub(crate) counter: u8, |     counter: u8, | ||||||
|     /// 0xFF06 | TMA - Timer Modulo |     /// 0xFF06 | TMA - Timer Modulo | ||||||
|     pub(crate) modulo: u8, |     pub(crate) modulo: u8, | ||||||
|     /// 0xFF04 | DIV - Divider Register |     /// 0xFF04 | DIV - Divider Register | ||||||
|     pub(crate) divider: u16, |     pub(crate) divider: u16, | ||||||
|     prev_and_result: Option<u8>, |  | ||||||
|  |     and_result: Option<u8>, | ||||||
|     interrupt: bool, |     interrupt: bool, | ||||||
|  |     state: State, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Timer { | impl Timer { | ||||||
|     pub(crate) fn clock(&mut self) { |     pub(crate) fn tick(&mut self) { | ||||||
|  |         use State::*; | ||||||
|         use TimerSpeed::*; |         use TimerSpeed::*; | ||||||
|  |  | ||||||
|         self.divider = self.divider.wrapping_add(1); |         self.divider = self.divider.wrapping_add(1); | ||||||
| @@ -29,27 +32,45 @@ impl Timer { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let bit = (self.divider >> bit) as u8 & 0x01; |         let bit = (self.divider >> bit) as u8 & 0x01; | ||||||
|         let timer_enable = self.ctrl.enabled() as u8; |         let new_result = bit & self.ctrl.enabled() as u8; | ||||||
|         let and_result = bit & timer_enable; |  | ||||||
|  |  | ||||||
|         if let Some(0x01) = self.prev_and_result { |         if let Some(0x01) = self.and_result { | ||||||
|             if and_result == 0x00 { |             if new_result == 0x00 { | ||||||
|                 // Falling Edge, increase TIMA Register |                 // Falling Edge, increase TIMA Register | ||||||
|                 self.increment_tima(); |                 self.inc_counter(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.prev_and_result = Some(and_result); |         self.and_result = Some(new_result); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn increment_tima(&mut self) { |         match self.state { | ||||||
|         let (result, did_overflow) = self.counter.overflowing_add(1); |             TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(), | ||||||
|  |             LoadTIMA => { | ||||||
|         self.counter = if did_overflow { |                 self.counter = self.modulo; | ||||||
|                 self.interrupt = true; |                 self.interrupt = true; | ||||||
|             self.modulo |  | ||||||
|         } else { |                 self.next(); | ||||||
|             result |             } | ||||||
|  |             Normal => {} | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// 0xFF05 | TIMA - Timer Counter | ||||||
|  |     pub(crate) fn tima(&self) -> u8 { | ||||||
|  |         self.counter | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// 0xFF05 | TIMA - Timer Counter | ||||||
|  |     pub(crate) fn set_tima(&mut self, byte: u8) { | ||||||
|  |         use State::*; | ||||||
|  |  | ||||||
|  |         match self.state { | ||||||
|  |             Normal | AbortedTIMAOverflow(_) => self.counter = byte, | ||||||
|  |             TIMAOverflow(step) => { | ||||||
|  |                 self.counter = byte; | ||||||
|  |                 self.state = AbortedTIMAOverflow(step); | ||||||
|  |             } | ||||||
|  |             LoadTIMA => { /* Ignored */ } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -60,17 +81,38 @@ impl Timer { | |||||||
|     pub(crate) fn set_interrupt(&mut self, value: bool) { |     pub(crate) fn set_interrupt(&mut self, value: bool) { | ||||||
|         self.interrupt = value; |         self.interrupt = value; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn inc_counter(&mut self) { | ||||||
|  |         let (sum, did_overflow) = self.counter.overflowing_add(1); | ||||||
|  |         self.counter = if did_overflow { 0 } else { sum }; | ||||||
|  |  | ||||||
|  |         if did_overflow { | ||||||
|  |             self.state = State::TIMAOverflow(0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn next(&mut self) { | ||||||
|  |         use State::*; | ||||||
|  |  | ||||||
|  |         self.state = match self.state { | ||||||
|  |             Normal | LoadTIMA | AbortedTIMAOverflow(3) => Normal, | ||||||
|  |             TIMAOverflow(3) => LoadTIMA, | ||||||
|  |             AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1), | ||||||
|  |             TIMAOverflow(step) => TIMAOverflow(step + 1), | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Timer { | impl Default for Timer { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             ctrl: Default::default(), |             ctrl: TimerControl(0), | ||||||
|             counter: 0, |             counter: 0, | ||||||
|             modulo: 0, |             modulo: 0, | ||||||
|             divider: 0, |             divider: 0, | ||||||
|             interrupt: false, |             interrupt: false, | ||||||
|             prev_and_result: None, |             and_result: None, | ||||||
|  |             state: State::Normal, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -115,12 +157,6 @@ impl Clone for TimerControl { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for TimerControl { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(0) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl From<u8> for TimerControl { | impl From<u8> for TimerControl { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         Self(byte) |         Self(byte) | ||||||
| @@ -132,3 +168,11 @@ impl From<TimerControl> for u8 { | |||||||
|         ctrl.0 |         ctrl.0 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
|  | enum State { | ||||||
|  |     TIMAOverflow(u8), | ||||||
|  |     AbortedTIMAOverflow(u8), | ||||||
|  |     Normal, | ||||||
|  |     LoadTIMA, | ||||||
|  | } | ||||||
|   | |||||||
| @@ -5,19 +5,19 @@ const VARIABLE_WORK_RAM_SIZE: usize = WORK_RAM_SIZE; | |||||||
| const WORK_RAM_START_ADDRESS: usize = 0xC000; | const WORK_RAM_START_ADDRESS: usize = 0xC000; | ||||||
| const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000; | const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000; | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug)] | ||||||
| pub(crate) struct WorkRam { | pub(crate) struct WorkRam { | ||||||
|     bank: Box<[u8; WORK_RAM_SIZE]>, |     bank: Box<[u8; WORK_RAM_SIZE]>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl BusIo for WorkRam { | impl BusIo for WorkRam { | ||||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { |  | ||||||
|         self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn read_byte(&self, addr: u16) -> u8 { |     fn read_byte(&self, addr: u16) -> u8 { | ||||||
|         self.bank[addr as usize - WORK_RAM_START_ADDRESS] |         self.bank[addr as usize - WORK_RAM_START_ADDRESS] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||||
|  |         self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for WorkRam { | impl Default for WorkRam { | ||||||
| @@ -28,48 +28,25 @@ impl Default for WorkRam { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug)] | ||||||
| enum BankNumber { |  | ||||||
|     One = 1, |  | ||||||
|     Two = 2, |  | ||||||
|     Three = 3, |  | ||||||
|     Four = 4, |  | ||||||
|     Five = 5, |  | ||||||
|     Six = 6, |  | ||||||
|     Seven = 7, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub(crate) struct VariableWorkRam { | pub(crate) struct VariableWorkRam { | ||||||
|     current: BankNumber, |     buf: Box<[u8; VARIABLE_WORK_RAM_SIZE]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour | ||||||
|     bank_n: Box<[[u8; VARIABLE_WORK_RAM_SIZE]; 7]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for VariableWorkRam { | impl Default for VariableWorkRam { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             current: BankNumber::One, |             buf: Box::new([0u8; VARIABLE_WORK_RAM_SIZE]), | ||||||
|             bank_n: Box::new([[0u8; VARIABLE_WORK_RAM_SIZE]; 7]), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl VariableWorkRam { |  | ||||||
|     fn set_current_bank(&mut self, bank: BankNumber) { |  | ||||||
|         self.current = bank; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn get_current_bank(&self) -> BankNumber { |  | ||||||
|         self.current |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl BusIo for VariableWorkRam { | impl BusIo for VariableWorkRam { | ||||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { |     fn read_byte(&self, addr: u16) -> u8 { | ||||||
|         self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte; |         self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn read_byte(&self, addr: u16) -> u8 { |     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||||
|         self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] |         self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user