Compare commits

...

52 Commits

Author SHA1 Message Date
8a352ca807 chore: prevent bitrot 2024-10-13 14:59:24 -05:00
068e4124a6 chore: update dependencies 2024-10-13 14:46:46 -05:00
fedae65cc8 chore: update dependencies 2023-03-17 23:42:51 -05:00
41586d8cc4 chore: update gilrs 2023-02-03 15:20:38 -06:00
79740ac245 chore: update default presenting mode 2022-11-09 08:19:15 -04:00
d6bfde081e feat(ppu): implement double buffering 2022-11-05 02:02:41 -03:00
d2f57ee66b chore: update clap to 4.0 2022-11-05 01:57:44 -03:00
3841d3a98c chore(readme): add build instructions
closes #1
2022-10-26 21:05:00 -03:00
85940c8744 fix: actually paint the gui 2022-10-21 01:17:02 -03:00
c7adf54976 chore: run cargo fmt 2022-10-21 01:13:44 -03:00
f0f9003247 chore: update wgpu 2022-10-21 00:46:14 -03:00
5eba097ad4 chore: update recommended vscode extensions 2022-10-21 00:14:10 -03:00
756c806c2a chore: satisfy clippy 2022-09-12 07:06:09 -03:00
77af5453f0 chore: refactor gui code 2022-09-12 07:00:50 -03:00
41bc4f7afe chore: update dependencies 2022-09-12 06:01:34 -03:00
36e46d3780 chore: update dependencies 2022-04-26 11:45:57 -05:00
926c66746d chore: update some dependencies 2022-04-07 15:30:46 -03:00
cf2357f917 feat: add disassembler that is aware of immediate values 2021-12-16 12:16:58 -04:00
3fb7edd333 chore: add some missing comments to instruction decoding 2021-12-16 12:16:36 -04:00
3ab512d663 fix(ppu): implement proper PPU reset behaviour 2021-12-09 10:46:27 -04:00
61156ca8a8 fix(cart): rename enum variant and enable MBC3 w/ battery 2021-12-09 09:30:47 -04:00
2c411d5792 chore(cart): add test for all-whitespace title 2021-12-09 07:27:52 -04:00
4c657c9832 chore: update dependencies 2021-12-09 06:23:32 -04:00
e72b11f946 feat: add more debug tools to gui 2021-12-09 05:21:05 -04:00
bbcbbd8ce3 chore: fix dependency conflict 2021-12-06 13:36:23 -04:00
c4226e6e76 Merge branch 'main' of ssh://git.musuka.dev:2222/paoda/gb into main 2021-12-06 13:34:14 -04:00
409314a4e5 fix(dbg): don't crash when attempting to read data as executable code 2021-12-06 13:33:22 -04:00
952bf68bd0 chore: add extension to recommended 2021-12-06 13:24:05 -04:00
865c11d53c chore: update dependencies 2021-12-03 16:34:12 -04:00
90d2da9272 chore(gui): implement basic disassembler
Also implement CPU and Interrupt debug information.

Of note:
1. IE and IRQ status boxes are slightly misaligned
2. Whenever the disassembler accidentally reads into game data rather
   than executable code the emulator crashes
   * Thus I should turn Instruction decoding into a Result<> rather than
     panic on failure
2021-11-30 10:23:06 -04:00
4a1a21a08f chore: satisfy clippy 2021-11-30 04:13:12 -04:00
9d23b571fb chore: update cargo.toml 2021-11-30 04:00:34 -04:00
70e0b5868d chore: code cleanup 2021-11-30 03:59:44 -04:00
a0e3c7c602 chore: remove useless default implementations 2021-11-25 04:07:45 -04:00
ef4f3d9ec6 chore(cart): refactor sections of cartridge code 2021-11-25 03:32:22 -04:00
7f872a554b chore(cart): code cleanup 2021-11-25 03:32:18 -04:00
dda0257655 fix(cart): support cartridge headers w/ manufactor codes 2021-11-25 00:12:35 -04:00
c10816c048 chore: imrpove private and public APIs 2021-11-24 22:12:26 -04:00
2405fd027f chore: remove Emulator struct 2021-11-21 05:27:04 -04:00
939c25ce1a chore: update comment 2021-11-15 22:05:26 -04:00
6f5e863645 chore: drop pixels-rs and add egui w/ wgpu backend 2021-11-15 21:34:24 -04:00
ce5d58e1d2 chore(ppu): rename some structs 2021-11-07 14:37:40 -06:00
ebcd5bec5c fix(cart): reset sub-cycle counting in RTC
specifically when RTC S is written to
2021-11-07 13:05:41 -06:00
f2c49b398c chore(cart): improve code quality of RtcClock 2021-11-05 23:05:36 -03:00
6f76571d6c chore: update README 2021-11-05 22:40:31 -03:00
55da5a29d8 chore: remove more getters 2021-11-01 18:04:22 +09:00
7dfcd44820 chore(emu): remove bus and bus_mut from cpu 2021-11-01 17:56:42 +09:00
80a15039e0 chore(apu): improve accuracy of Channel 1 2021-10-30 21:53:08 +09:00
32405c0734 fix(apu): implement some obscure behaviour for ch1,2 & 3 2021-10-30 20:04:16 +09:00
2ef8fefbb7 chore(apu): implement more obscure behaviour
implement capacitor
2021-10-30 18:41:31 +09:00
548945a4ee chore: remove imports of tracing macros 2021-10-30 16:41:06 +09:00
1526b4a09d fix(cart): improve title parsin
Now both Tokimeki Memorial titles are read properly
2021-10-30 15:48:47 +09:00
24 changed files with 3615 additions and 2182 deletions

View File

@@ -1,8 +1,9 @@
{ {
"recommendations": [ "recommendations": [
"matklad.rust-analyzer", "rust-lang.rust-analyzer",
"tamasfe.even-better-toml", "tamasfe.even-better-toml",
"serayuzgur.crates", "serayuzgur.crates",
"vadimcn.vscode-lldb", "vadimcn.vscode-lldb",
"donaldhays.rgbds-z80"
] ]
} }

2625
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,17 +8,21 @@ edition = "2021"
[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.7" winit = "0.28"
winit = "0.25" egui = "0.21"
winit_input_helper = "0.10" egui_wgpu_backend = "0.22"
rodio = "0.14" egui_winit_platform = "0.18"
rtrb = "0.2" pollster = "0.3"
rodio = "0.19"
rtrb = "0.3"
directories-next = "2.0" directories-next = "2.0"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
thiserror = "1.0"
once_cell = "1.10"
[profile.release] [profile.release]
debug = true debug = true

View File

@@ -1,5 +1,4 @@
# Rekai's Gameboy Emulator # Rekai's Gameboy Emulator
[![Build Status](https://ci.paoda.moe/api/badges/paoda/gb/status.svg)](https://ci.paoda.moe/paoda/gb)
### Status ### Status
* From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/) * From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
@@ -7,7 +6,7 @@
* [x] instr_timing * [x] instr_timing
* [x] mem_timing * [x] mem_timing
* [x] mem_timing-2 * [x] mem_timing-2
* [ ] Partially dmg_sound * [ ] dmg_sound (partial)
* [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) * [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2)
* From [mooneye-gb](https://github.com/Gekkio/mooneye-gb): * From [mooneye-gb](https://github.com/Gekkio/mooneye-gb):
* Cartridges: * Cartridges:
@@ -16,14 +15,20 @@
* [x] MBC2 * [x] MBC2
* [x] MBC5 * [x] MBC5
* Implements a cycle-accurate PPU FIFO * Implements a cycle-accurate PPU FIFO
* Doesn't \*exactly\* work right just yet * Doesn't \*exactly\* work just yet
Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games. Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games.
### Compiling
This project was last successfully built on [Rust 1.64.0](https://github.com/rust-lang/rust/tree/1.64.0)
1. `git clone https://github.com/paoda/gb`
2. `cd gb`
3. `cargo run --release`
### Controls ### Controls
Controls are defined [here](https://git.musuka.dev/paoda/gb/src/branch/main/src/joypad.rs#L114) Controls are defined [here](https://github.com/paoda/gb/blob/85940c874460b9cb31bf9b8211bf7dda596d114a/src/joypad.rs#L114)
Key | Button Key | Button
--- | --- --- | ---

View File

@@ -1,13 +1,13 @@
use crate::apu::gen::SAMPLE_RATE;
use crate::bus::BusIo; use crate::bus::BusIo;
use crate::emu::SM83_CLOCK_SPEED; use crate::emu::SM83_CLOCK_SPEED;
use gen::SampleProducer; use gen::SampleProducer;
use tracing::warn;
use types::ch1::{Sweep, SweepDirection}; use types::ch1::{Sweep, SweepDirection};
use types::ch3::Volume as Ch3Volume; use types::ch3::Volume as Ch3Volume;
use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter}; use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter};
use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope}; use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope};
use types::fs::{FrameSequencer, State as FrameSequencerState}; use types::fs::{FrameSequencer, State as FSState};
use types::{ChannelControl, SoundOutput}; use types::{ChannelControl, NRx4, SoundOutput};
pub mod gen; pub mod gen;
mod types; mod types;
@@ -27,11 +27,13 @@ pub struct Apu {
/// Noise /// Noise
ch4: Channel4, ch4: Channel4,
sequencer: FrameSequencer, fs: FrameSequencer,
div_prev: Option<u16>, div_prev: Option<u16>,
prod: Option<SampleProducer<f32>>, pub(crate) prod: Option<SampleProducer<f32>>,
sample_counter: u64, sample_counter: u64,
cap: f32,
} }
impl BusIo for Apu { impl BusIo for Apu {
@@ -55,7 +57,7 @@ impl BusIo for Apu {
0x26 => self.ctrl.status(self), 0x26 => self.ctrl.status(self),
0x30..=0x3F => self.ch3.read_byte(addr), 0x30..=0x3F => self.ch3.read_byte(addr),
_ => { _ => {
warn!("Attempted read from {:#06X}", addr); tracing::warn!("Attempted read from {:#06X}", addr);
0xFF 0xFF
} }
} }
@@ -67,26 +69,26 @@ impl BusIo for Apu {
0x11 if self.ctrl.enabled => self.ch1.set_duty(byte), 0x11 if self.ctrl.enabled => self.ch1.set_duty(byte),
0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte), 0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte),
0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte), 0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte),
0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(byte), 0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(&self.fs, byte),
0x16 if self.ctrl.enabled => self.ch2.set_duty(byte), 0x16 if self.ctrl.enabled => self.ch2.set_duty(byte),
0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte), 0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte),
0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(byte), 0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(byte),
0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(byte), 0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(&self.fs, byte),
0x1A if self.ctrl.enabled => self.ch3.set_dac_enabled(byte), 0x1A if self.ctrl.enabled => self.ch3.set_dac_enabled(byte),
0x1B if self.ctrl.enabled => self.ch3.set_len(byte), 0x1B if self.ctrl.enabled => self.ch3.set_len(byte),
0x1C if self.ctrl.enabled => self.ch3.set_volume(byte), 0x1C if self.ctrl.enabled => self.ch3.set_volume(byte),
0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(byte), 0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(byte),
0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(byte), 0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(&self.fs, byte),
0x20 if self.ctrl.enabled => self.ch4.set_len(byte), 0x20 if self.ctrl.enabled => self.ch4.set_len(byte),
0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte), 0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte),
0x22 if self.ctrl.enabled => self.ch4.set_poly(byte), 0x22 if self.ctrl.enabled => self.ch4.set_poly(byte),
0x23 if self.ctrl.enabled => self.ch4.set_frequency(byte), 0x23 if self.ctrl.enabled => self.ch4.set_frequency(&self.fs, byte),
0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte), 0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte),
0x25 if self.ctrl.enabled => self.ctrl.set_output(byte), 0x25 if self.ctrl.enabled => self.ctrl.set_output(byte),
0x26 => self.set_status(byte), 0x26 => self.set_status(byte),
0x30..=0x3F => self.ch3.write_byte(addr, byte), 0x30..=0x3F => self.ch3.write_byte(addr, byte),
_ if !self.ctrl.enabled => {} _ if !self.ctrl.enabled => {}
_ => warn!("Attempted write of {:#04X} to {:#06X}", byte, addr), _ => tracing::warn!("Attempted write of {:#04X} to {:#06X}", byte, addr),
} }
} }
} }
@@ -97,9 +99,9 @@ impl Apu {
// Frame Sequencer (512Hz) // Frame Sequencer (512Hz)
if self.is_falling_edge(12, div) { if self.is_falling_edge(12, div) {
use FrameSequencerState::*; use FSState::*;
match self.sequencer.state() { match self.fs.state() {
Length => self.clock_length(), Length => self.clock_length(),
LengthAndSweep => { LengthAndSweep => {
self.clock_length(); self.clock_length();
@@ -109,7 +111,7 @@ impl Apu {
Nothing => {} Nothing => {}
} }
self.sequencer.next(); self.fs.next();
} }
self.div_prev = Some(div); self.div_prev = Some(div);
@@ -122,25 +124,33 @@ impl Apu {
if self.sample_counter >= SM83_CLOCK_SPEED { if self.sample_counter >= SM83_CLOCK_SPEED {
self.sample_counter %= SM83_CLOCK_SPEED; self.sample_counter %= SM83_CLOCK_SPEED;
if let Some(ref mut prod) = self.prod { if let Some(prod) = self.prod.as_mut() {
if prod.available_blocking() { if prod.available_blocking() {
// Sample the APU // Sample the APU
let ch1_amplitude =
Self::high_pass(&mut self.cap, self.ch1.amplitude(), self.ch1.enabled);
let (left, right) = self.ctrl.out.ch1(); let (left, right) = self.ctrl.out.ch1();
let ch1_left = if left { self.ch1.amplitude() } else { 0.0 }; let ch1_left = if left { ch1_amplitude } else { 0.0 };
let ch1_right = if right { self.ch1.amplitude() } else { 0.0 }; let ch1_right = if right { ch1_amplitude } else { 0.0 };
let ch2_amplitude =
Self::high_pass(&mut self.cap, self.ch2.amplitude(), self.ch2.enabled);
let (left, right) = self.ctrl.out.ch2(); let (left, right) = self.ctrl.out.ch2();
let ch2_left = if left { self.ch2.amplitude() } else { 0.0 }; let ch2_left = if left { ch2_amplitude } else { 0.0 };
let ch2_right = if right { self.ch2.amplitude() } else { 0.0 }; let ch2_right = if right { ch2_amplitude } else { 0.0 };
let ch3_amplitude =
Self::high_pass(&mut self.cap, self.ch3.amplitude(), self.ch3.enabled);
let (left, right) = self.ctrl.out.ch3(); let (left, right) = self.ctrl.out.ch3();
let ch3_left = if left { self.ch3.amplitude() } else { 0.0 }; let ch3_left = if left { ch3_amplitude } else { 0.0 };
let ch3_right = if right { self.ch3.amplitude() } else { 0.0 }; let ch3_right = if right { ch3_amplitude } else { 0.0 };
let ch4_amplitude =
Self::high_pass(&mut self.cap, self.ch4.amplitude(), self.ch4.enabled);
let (left, right) = self.ctrl.out.ch4(); let (left, right) = self.ctrl.out.ch4();
let ch4_left = if left { self.ch4.amplitude() } else { 0.0 }; let ch4_left = if left { ch4_amplitude } else { 0.0 };
let ch4_right = if right { self.ch4.amplitude() } else { 0.0 }; let ch4_right = if right { ch4_amplitude } else { 0.0 };
let left_mixed = (ch1_left + ch2_left + ch3_left + ch4_left) / 4.0; let left_mixed = (ch1_left + ch2_left + ch3_left + ch4_left) / 4.0;
let right_mixed = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0; let right_mixed = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0;
@@ -162,7 +172,7 @@ impl Apu {
if self.ctrl.enabled { if self.ctrl.enabled {
// Frame Sequencer reset to Step 0 // Frame Sequencer reset to Step 0
self.sequencer.reset(); self.fs.reset();
// Square Duty units are reset to first step // Square Duty units are reset to first step
self.ch1.duty_pos = 0; self.ch1.duty_pos = 0;
@@ -178,10 +188,6 @@ impl Apu {
} }
} }
pub fn attach_producer(&mut self, prod: SampleProducer<f32>) {
self.prod = Some(prod);
}
fn reset(&mut self) { fn reset(&mut self) {
self.ch1.sweep = Default::default(); self.ch1.sweep = Default::default();
self.ch1.duty = Default::default(); self.ch1.duty = Default::default();
@@ -205,8 +211,8 @@ impl Apu {
self.ch4.poly = Default::default(); self.ch4.poly = Default::default();
self.ch4.freq = Default::default(); self.ch4.freq = Default::default();
self.ctrl.channel = Default::default(); self.ctrl.channel = ChannelControl(0);
self.ctrl.out = Default::default(); self.ctrl.out = SoundOutput(0);
// Disable the Channels // Disable the Channels
self.ch1.enabled = Default::default(); self.ch1.enabled = Default::default();
@@ -215,25 +221,13 @@ impl Apu {
self.ch4.enabled = Default::default(); self.ch4.enabled = Default::default();
} }
fn process_length(freq_hi: &FrequencyHigh, length_timer: &mut u16, enabled: &mut bool) { fn process_length(freq: &impl NRx4, counter: &mut u16, enabled: &mut bool) {
if freq_hi.length_disable() && *length_timer > 0 { if freq.length_enable() && *counter > 0 {
*length_timer -= 1; *counter -= 1;
// Check in this scope ensures (only) the above subtraction // Check in this scope ensures (only) the above subtraction
// made length_timer 0 // made length_timer 0
if *length_timer == 0 { if *counter == 0 {
*enabled = false;
}
}
}
fn ch4_process_length(freq: &Ch4Frequency, length_timer: &mut u16, enabled: &mut bool) {
if freq.length_disable() && *length_timer > 0 {
*length_timer -= 1;
// Check in this scope ensures (only) the above subtraction
// made length_timer 0
if *length_timer == 0 {
*enabled = false; *enabled = false;
} }
} }
@@ -242,25 +236,25 @@ impl Apu {
fn clock_length(&mut self) { fn clock_length(&mut self) {
Self::process_length( Self::process_length(
&self.ch1.freq_hi, &self.ch1.freq_hi,
&mut self.ch1.length_timer, &mut self.ch1.length_counter,
&mut self.ch1.enabled, &mut self.ch1.enabled,
); );
Self::process_length( Self::process_length(
&self.ch2.freq_hi, &self.ch2.freq_hi,
&mut self.ch2.length_timer, &mut self.ch2.length_counter,
&mut self.ch2.enabled, &mut self.ch2.enabled,
); );
Self::process_length( Self::process_length(
&self.ch3.freq_hi, &self.ch3.freq_hi,
&mut self.ch3.length_timer, &mut self.ch3.length_counter,
&mut self.ch3.enabled, &mut self.ch3.enabled,
); );
Self::ch4_process_length( Self::process_length(
&self.ch4.freq, &self.ch4.freq,
&mut self.ch4.length_timer, &mut self.ch4.length_counter,
&mut self.ch4.enabled, &mut self.ch4.enabled,
); );
} }
@@ -277,7 +271,7 @@ impl Apu {
if self.ch1.sweep_enabled && period != 0 { if self.ch1.sweep_enabled && period != 0 {
let new_freq = self.ch1.calc_sweep_freq(); let new_freq = self.ch1.calc_sweep_freq();
if new_freq <= 2047 && self.ch1.sweep.shift_count() != 0 { if new_freq <= 0x7FF && self.ch1.sweep.shift_count() != 0 {
self.ch1.set_frequency(new_freq); self.ch1.set_frequency(new_freq);
self.ch1.shadow_freq = new_freq; self.ch1.shadow_freq = new_freq;
@@ -335,9 +329,22 @@ impl Apu {
None => false, None => false,
} }
} }
fn high_pass(capacitor: &mut f32, input: f32, enabled: bool) -> f32 {
const CHARGE_FACTOR: f32 = 0.999958;
let mut output = 0.0;
if enabled {
output = input - *capacitor;
*capacitor =
input - output * CHARGE_FACTOR.powi(SM83_CLOCK_SPEED as i32 / SAMPLE_RATE as i32);
} }
#[derive(Debug, Default)] output
}
}
#[derive(Debug)]
pub(crate) struct SoundControl { pub(crate) struct SoundControl {
/// 0xFF24 | NR50 - Channel Control /// 0xFF24 | NR50 - Channel Control
channel: ChannelControl, channel: ChannelControl,
@@ -347,6 +354,16 @@ pub(crate) struct SoundControl {
enabled: bool, enabled: bool,
} }
impl Default for SoundControl {
fn default() -> Self {
Self {
channel: ChannelControl(0),
out: SoundOutput(0),
enabled: Default::default(),
}
}
}
impl SoundControl { impl SoundControl {
/// 0xFF24 | NR50 - Channel Control /// 0xFF24 | NR50 - Channel Control
pub(crate) fn channel(&self) -> u8 { pub(crate) fn channel(&self) -> u8 {
@@ -406,7 +423,7 @@ pub(crate) struct Channel1 {
sweep_enabled: bool, sweep_enabled: bool,
// Length Functionality // Length Functionality
length_timer: u16, length_counter: u16,
freq_timer: u16, freq_timer: u16,
duty_pos: u8, duty_pos: u8,
@@ -433,7 +450,7 @@ impl Channel1 {
/// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty /// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty
pub(crate) fn set_duty(&mut self, byte: u8) { pub(crate) fn set_duty(&mut self, byte: u8) {
self.duty = byte.into(); self.duty = byte.into();
self.length_timer = 64 - self.duty.sound_length() as u16; self.length_counter = 64 - self.duty.sound_length() as u16;
} }
/// 0xFF12 | NR12 - Channel 1 Volume Envelope /// 0xFF12 | NR12 - Channel 1 Volume Envelope
@@ -461,25 +478,30 @@ impl Channel1 {
} }
/// 0xFF14 | NR14 - Channel 1 Frequency high /// 0xFF14 | NR14 - Channel 1 Frequency high
pub(crate) fn set_freq_hi(&mut self, byte: u8) { pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
self.freq_hi = byte.into(); let mut new_freq: FrequencyHigh = byte.into();
// If this bit is set, a trigger event occurs // If this bit is set, a trigger event occurs
if self.freq_hi.initial() { if new_freq.trigger() {
if self.is_dac_enabled() {
self.enabled = true; self.enabled = true;
}
// Length behaviour during trigger event if self.length_counter == 0 {
if self.length_timer == 0 { self.length_counter = 64;
self.length_timer = 64;
}
// Envelope Behaviour during trigger event // length_counter was 0 so length evidently wasn't enabled
self.freq_hi.set_length_enable(false);
};
// reload freq_timer but last two bits are unmodified
self.freq_timer = obscure::square::freq_timer_reload(self.freq_timer, self.frequency());
// Volume Envelope loaded w/ period
self.period_timer = self.envelope.period(); self.period_timer = self.envelope.period();
// Channel Volume reloaded
self.current_volume = self.envelope.init_vol(); self.current_volume = self.envelope.init_vol();
// Sweep behaviour during trigger event // Channel 1 Sweep Behaviour
let sweep_period = self.sweep.period(); let sweep_period = self.sweep.period();
let sweep_shift = self.sweep.shift_count(); let sweep_shift = self.sweep.shift_count();
@@ -491,7 +513,19 @@ impl Channel1 {
if sweep_shift != 0 { if sweep_shift != 0 {
let _ = self.calc_sweep_freq(); let _ = self.calc_sweep_freq();
} }
self.enabled = self.is_dac_enabled();
} }
obscure::nrx4::length_update(
&mut new_freq,
fs,
&mut self.length_counter,
&mut self.enabled,
self.freq_hi.length_enable(),
);
self.freq_hi = new_freq;
} }
fn tick(&mut self) { fn tick(&mut self) {
@@ -553,7 +587,7 @@ impl Channel1 {
pub(crate) struct Channel2 { pub(crate) struct Channel2 {
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
duty: SoundDuty, duty: SoundDuty,
/// 0xFF17 | NR22 - Channel 2 Volume ENvelope /// 0xFF17 | NR22 - Channel 2 Volume Envelope
envelope: VolumeEnvelope, envelope: VolumeEnvelope,
/// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only) /// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
freq_lo: u8, freq_lo: u8,
@@ -565,7 +599,7 @@ pub(crate) struct Channel2 {
current_volume: u8, current_volume: u8,
// Length Functionality // Length Functionality
length_timer: u16, length_counter: u16,
freq_timer: u16, freq_timer: u16,
duty_pos: u8, duty_pos: u8,
@@ -582,15 +616,15 @@ impl Channel2 {
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
pub(crate) fn set_duty(&mut self, byte: u8) { pub(crate) fn set_duty(&mut self, byte: u8) {
self.duty = byte.into(); self.duty = byte.into();
self.length_timer = 64 - self.duty.sound_length() as u16; self.length_counter = 64 - self.duty.sound_length() as u16;
} }
/// 0xFF17 | NR22 - Channel 2 Volume ENvelope /// 0xFF17 | NR22 - Channel 2 Volume Envelope
pub(crate) fn envelope(&self) -> u8 { pub(crate) fn envelope(&self) -> u8 {
u8::from(self.envelope) u8::from(self.envelope)
} }
/// 0xFF17 | NR22 - Channel 2 Volume ENvelope /// 0xFF17 | NR22 - Channel 2 Volume Envelope
pub(crate) fn set_envelope(&mut self, byte: u8) { pub(crate) fn set_envelope(&mut self, byte: u8) {
self.envelope = byte.into(); self.envelope = byte.into();
@@ -610,22 +644,36 @@ impl Channel2 {
} }
/// 0xFF19 | NR24 - Channel 2 Frequency high /// 0xFF19 | NR24 - Channel 2 Frequency high
pub(crate) fn set_freq_hi(&mut self, byte: u8) { pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
let prev_le = self.freq_hi.length_enable();
self.freq_hi = byte.into(); self.freq_hi = byte.into();
if self.freq_hi.initial() { obscure::nrx4::length_update(
// Envelope behaviour during trigger event &mut self.freq_hi,
fs,
&mut self.length_counter,
&mut self.enabled,
prev_le,
);
if self.freq_hi.trigger() {
self.enabled = true;
// Reload length counter if need be
if self.length_counter == 0 {
self.length_counter = 64;
}
// reload frequency timer
self.freq_timer = obscure::square::freq_timer_reload(self.freq_timer, self.frequency());
// reload envelope
self.period_timer = self.envelope.period(); self.period_timer = self.envelope.period();
// reload volume
self.current_volume = self.envelope.init_vol(); self.current_volume = self.envelope.init_vol();
// Length behaviour during trigger event self.enabled = self.is_dac_enabled();
if self.length_timer == 0 {
self.length_timer = 64;
}
if self.is_dac_enabled() {
self.enabled = true;
}
} }
} }
@@ -676,7 +724,7 @@ pub(crate) struct Channel3 {
wave_ram: [u8; WAVE_PATTERN_RAM_LEN], wave_ram: [u8; WAVE_PATTERN_RAM_LEN],
// Length Functionality // Length Functionality
length_timer: u16, length_counter: u16,
freq_timer: u16, freq_timer: u16,
offset: u8, offset: u8,
@@ -722,7 +770,7 @@ impl Channel3 {
/// 0xFF1B | NR31 - Sound Length /// 0xFF1B | NR31 - Sound Length
pub(crate) fn set_len(&mut self, byte: u8) { pub(crate) fn set_len(&mut self, byte: u8) {
self.len = byte; self.len = byte;
self.length_timer = 256 - self.len as u16; self.length_counter = 256 - self.len as u16;
} }
/// 0xFF1C | NR32 - Channel 3 Volume /// 0xFF1C | NR32 - Channel 3 Volume
@@ -754,18 +802,32 @@ impl Channel3 {
} }
/// 0xFF1E | NR34 - Channel 3 Frequency high /// 0xFF1E | NR34 - Channel 3 Frequency high
pub(crate) fn set_freq_hi(&mut self, byte: u8) { pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
let prev_le = self.freq_hi.length_enable();
self.freq_hi = byte.into(); self.freq_hi = byte.into();
if self.freq_hi.initial() { obscure::nrx4::length_update(
&mut self.freq_hi,
fs,
&mut self.length_counter,
&mut self.enabled,
prev_le,
);
if self.freq_hi.trigger() {
self.enabled = true;
// Length behaviour during trigger event // Length behaviour during trigger event
if self.length_timer == 0 { if self.length_counter == 0 {
self.length_timer = 256; self.length_counter = 256;
} }
if self.dac_enabled { self.freq_timer = (2048 - self.frequency()) * 2;
self.enabled = true;
} // reset wave channel's ptr into wave RAM
self.offset = 0;
self.enabled = self.dac_enabled;
} }
} }
@@ -824,7 +886,7 @@ pub(crate) struct Channel4 {
current_volume: u8, current_volume: u8,
// Length Functionality // Length Functionality
length_timer: u16, length_counter: u16,
/// Linear Feedback Shift Register (15-bit) /// Linear Feedback Shift Register (15-bit)
lf_shift: u16, lf_shift: u16,
@@ -838,7 +900,7 @@ impl Channel4 {
/// 0xFF20 | NR41 - Channel 4 Sound Length /// 0xFF20 | NR41 - Channel 4 Sound Length
pub(crate) fn set_len(&mut self, byte: u8) { pub(crate) fn set_len(&mut self, byte: u8) {
self.len = byte & 0x3F; self.len = byte & 0x3F;
self.length_timer = 64 - self.len as u16; self.length_counter = 64 - self.len as u16;
} }
/// 0xFF21 | NR42 - Channel 4 Volume Envelope /// 0xFF21 | NR42 - Channel 4 Volume Envelope
@@ -871,25 +933,37 @@ impl Channel4 {
} }
/// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart
pub(crate) fn set_frequency(&mut self, byte: u8) { pub(crate) fn set_frequency(&mut self, fs: &FrameSequencer, byte: u8) {
let prev_le = self.freq.length_enable();
self.freq = byte.into(); self.freq = byte.into();
if self.freq.initial() { obscure::nrx4::length_update(
// Envelope behaviour during trigger event &mut self.freq,
fs,
&mut self.length_counter,
&mut self.enabled,
prev_le,
);
if self.freq.trigger() {
self.enabled = true;
if self.length_counter == 0 {
self.length_counter = 64;
}
// FIXME: Frequency Timer reloaded?
// reload envelope
self.period_timer = self.envelope.period(); self.period_timer = self.envelope.period();
//reload volume
self.current_volume = self.envelope.init_vol(); self.current_volume = self.envelope.init_vol();
// Length behaviour during trigger event // LFSR reset
if self.length_timer == 0 {
self.length_timer = 64;
}
// LFSR behaviour during trigger event
self.lf_shift = 0x7FFF; self.lf_shift = 0x7FFF;
if self.is_dac_enabled() { self.enabled = self.is_dac_enabled();
self.enabled = true;
}
} }
} }
@@ -934,3 +1008,39 @@ impl Channel4 {
code << 4 code << 4
} }
} }
mod obscure {
pub(super) mod square {
pub(crate) fn freq_timer_reload(freq_timer: u16, frequency: u16) -> u16 {
(freq_timer & 0x0003) | (((2048 - frequency) * 4) & 0xFFFC)
}
}
pub(super) mod nrx4 {
use super::super::{FrameSequencer, NRx4};
/// Implements the obscure behaviour when writing to NRX4 under certain
/// conditions
///
/// # Arguments
/// * `freq_hi` - mutable reference to a channel's frequency high register
/// * `fs` - reference to the APU's frame sequencer
/// * `counter` - mutable reference to a channel's internal enabled flag
/// * `prev_le` - what length_enable was before NRx4 was written with a new value
pub(crate) fn length_update(
freq: &mut impl NRx4,
fs: &FrameSequencer,
counter: &mut u16,
enabled: &mut bool,
prev_le: bool,
) {
if !fs.next_clocks_length() && !prev_le && freq.length_enable() && *counter != 0 {
*counter -= 1;
if *counter == 0 && !freq.trigger() {
*enabled = false;
}
}
}
}
}

View File

@@ -1,5 +1,11 @@
use bitfield::bitfield; 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 { pub(crate) mod ch1 {
use super::bitfield; use super::bitfield;
@@ -103,7 +109,7 @@ pub(super) mod ch3 {
} }
pub(super) mod ch4 { pub(super) mod ch4 {
use super::bitfield; use super::{bitfield, NRx4};
bitfield! { bitfield! {
pub struct PolynomialCounter(u8); pub struct PolynomialCounter(u8);
@@ -163,7 +169,7 @@ pub(super) mod ch4 {
match byte & 0x01 { match byte & 0x01 {
0b00 => Self::Short, 0b00 => Self::Short,
0b01 => Self::Long, 0b01 => Self::Long,
_ => unreachable!("{:#04X} is not a valid value for CounterWidth"), _ => unreachable!("{:#04X} is not a valid value for CounterWidth", byte),
} }
} }
} }
@@ -177,17 +183,21 @@ pub(super) mod ch4 {
bitfield! { bitfield! {
pub struct Frequency(u8); pub struct Frequency(u8);
impl Debug; impl Debug;
_initial, _: 7; _trigger, _: 7;
_length_disable, _: 6; _length_enable, _set_length_enable: 6;
} }
impl Frequency { impl NRx4 for Frequency {
pub(crate) fn length_disable(&self) -> bool { fn trigger(&self) -> bool {
self._length_disable() self._trigger()
} }
pub(crate) fn initial(&self) -> bool { fn length_enable(&self) -> bool {
self._initial() self._length_enable()
}
fn set_length_enable(&mut self, value: bool) {
self._set_length_enable(value);
} }
} }
@@ -218,23 +228,27 @@ pub(super) mod ch4 {
} }
pub(super) mod common { pub(super) mod common {
use super::bitfield; use super::{bitfield, NRx4};
bitfield! { bitfield! {
pub struct FrequencyHigh(u8); pub struct FrequencyHigh(u8);
impl Debug; impl Debug;
_initial, _: 7; _trigger, _: 7;
_length_disable, _: 6; _length_enable, _set_length_enable: 6;
pub freq_bits, set_freq_bits: 2, 0; pub freq_bits, set_freq_bits: 2, 0;
} }
impl FrequencyHigh { impl NRx4 for FrequencyHigh {
pub(crate) fn initial(&self) -> bool { fn trigger(&self) -> bool {
self._initial() self._trigger()
} }
pub(crate) fn length_disable(&self) -> bool { fn length_enable(&self) -> bool {
self._length_disable() self._length_enable()
}
fn set_length_enable(&mut self, value: bool) {
self._set_length_enable(value);
} }
} }
@@ -402,12 +416,6 @@ pub(super) mod common {
} }
} }
impl Default for WavePattern {
fn default() -> Self {
Self::OneEighth // Rationale: OneEighth is 0x00
}
}
impl From<WavePattern> for u8 { impl From<WavePattern> for u8 {
fn from(pattern: WavePattern) -> Self { fn from(pattern: WavePattern) -> Self {
pattern as Self pattern as Self
@@ -465,12 +473,6 @@ impl Clone for SoundOutput {
} }
} }
impl Default for SoundOutput {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for SoundOutput { impl From<u8> for SoundOutput {
fn from(byte: u8) -> Self { fn from(byte: u8) -> Self {
Self(byte) Self(byte)
@@ -509,12 +511,6 @@ impl Clone for ChannelControl {
} }
} }
impl Default for ChannelControl {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for ChannelControl { impl From<u8> for ChannelControl {
fn from(byte: u8) -> Self { fn from(byte: u8) -> Self {
Self(byte) Self(byte)
@@ -557,6 +553,15 @@ pub(super) mod fs {
}; };
} }
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 { pub(crate) fn state(&self) -> State {
self.state self.state
} }
@@ -567,7 +572,7 @@ pub(super) mod fs {
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum State { pub(crate) enum State {
Length, Length,
Nothing, Nothing,

View File

@@ -1,5 +1,3 @@
use tracing::warn;
use crate::apu::Apu; use crate::apu::Apu;
use crate::cartridge::Cartridge; use crate::cartridge::Cartridge;
use crate::high_ram::HighRam; use crate::high_ram::HighRam;
@@ -12,37 +10,19 @@ use crate::work_ram::{VariableWorkRam, WorkRam};
pub(crate) const BOOT_SIZE: usize = 0x100; pub(crate) const BOOT_SIZE: usize = 0x100;
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Bus { pub struct Bus {
boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
cart: Option<Cartridge>, pub(crate) cart: Option<Cartridge>,
ppu: Ppu, pub(crate) ppu: Ppu,
work_ram: WorkRam, work_ram: WorkRam,
var_ram: VariableWorkRam, var_ram: VariableWorkRam,
timer: Timer, timer: Timer,
int: Interrupt, int: Interrupt,
apu: Apu, pub(crate) apu: Apu,
high_ram: HighRam, high_ram: HighRam,
serial: Serial, serial: Serial,
joypad: Joypad, pub(crate) joyp: Joypad,
}
impl Default for Bus {
fn default() -> Self {
Self {
boot: None,
cart: None,
ppu: Default::default(),
work_ram: Default::default(),
var_ram: Default::default(),
timer: Default::default(),
int: Default::default(),
apu: Default::default(),
high_ram: Default::default(),
serial: Default::default(),
joypad: Default::default(),
}
}
} }
impl Bus { impl Bus {
@@ -53,14 +33,6 @@ impl Bus {
} }
} }
pub(crate) fn load_cart(&mut self, rom: Vec<u8>) {
self.cart = Some(Cartridge::new(rom));
}
pub(crate) fn cart_title(&self) -> Option<&str> {
self.cart.as_ref()?.title()
}
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) fn boot_mapped(&self) -> bool { pub(crate) fn boot_mapped(&self) -> bool {
self.boot.is_some() self.boot.is_some()
@@ -75,6 +47,10 @@ impl Bus {
fn tick(&mut self, limit: u8) { fn tick(&mut self, limit: u8) {
for _ in 0..limit { for _ in 0..limit {
self.timer.tick(); self.timer.tick();
if let Some(c) = self.cart.as_mut() {
c.tick()
}
self.ppu.tick(); self.ppu.tick();
self.apu.tick(self.timer.divider); self.apu.tick(self.timer.divider);
self.dma_tick() self.dma_tick()
@@ -87,31 +63,6 @@ impl Bus {
self.oam_write_byte(dest_addr, byte); self.oam_write_byte(dest_addr, byte);
} }
} }
#[inline]
pub(crate) fn joyp_mut(&mut self) -> &mut Joypad {
&mut self.joypad
}
#[inline]
pub(crate) fn cart(&self) -> Option<&Cartridge> {
self.cart.as_ref()
}
#[inline]
pub(crate) fn cart_mut(&mut self) -> Option<&mut Cartridge> {
self.cart.as_mut()
}
#[inline]
pub(crate) fn apu_mut(&mut self) -> &mut Apu {
&mut self.apu
}
#[inline]
pub fn ppu(&self) -> &Ppu {
&self.ppu
}
} }
impl Bus { impl Bus {
@@ -241,7 +192,7 @@ 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,
@@ -264,7 +215,7 @@ impl BusIo for Bus {
0x4B => self.ppu.pos.window_x, 0x4B => self.ppu.pos.window_x,
0x4F => 0xFF, // CGB VRAM Bank Select 0x4F => 0xFF, // CGB VRAM Bank Select
_ => { _ => {
warn!("Attempted read from {:#06X} on IO", addr); tracing::warn!("Attempted read from {:#06X} on IO", addr);
0xFF 0xFF
} }
} }
@@ -339,7 +290,7 @@ 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.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,
@@ -381,7 +332,7 @@ impl BusIo for Bus {
} }
} }
0x70 => {} // CGB WRAM Bank Select 0x70 => {} // CGB WRAM Bank Select
_ => warn!("Attempted write of {:#04X} to {:#06X} on IO", byte, addr), _ => tracing::warn!("Attempted write of {:#04X} to {:#06X} on IO", byte, addr),
}; };
} }
0xFF80..=0xFFFE => { 0xFF80..=0xFFFE => {
@@ -403,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();
@@ -433,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);

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ use crate::Cycle;
use bitfield::bitfield; use bitfield::bitfield;
use std::fmt::{Display, Formatter, Result as FmtResult}; use std::fmt::{Display, Formatter, Result as FmtResult};
#[derive(Debug, Default)] #[derive(Debug)]
pub struct Cpu { pub struct Cpu {
pub(crate) bus: Bus, pub(crate) bus: Bus,
reg: Registers, reg: Registers,
@@ -15,29 +15,13 @@ pub struct Cpu {
} }
impl Cpu { impl Cpu {
#[allow(dead_code)] pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> Self {
pub(crate) fn without_boot() -> Self {
Self {
reg: Registers {
a: 0x01,
b: 0x00,
c: 0x13,
d: 0x00,
e: 0xD8,
h: 0x01,
l: 0x4D,
sp: 0xFFFE,
pc: 0x0100,
},
flags: 0xb0.into(),
..Default::default()
}
}
pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self {
Self { Self {
bus: Bus::with_boot(rom), bus: Bus::with_boot(rom),
..Default::default() reg: Default::default(),
flags: Flags(0),
ime: ImeState::Disabled,
state: State::Execute,
} }
} }
@@ -69,6 +53,12 @@ impl Cpu {
} }
} }
impl Default for Cpu {
fn default() -> Self {
Cpu::new(*include_bytes!("../bin/bootix_dmg.bin"))
}
}
impl Cpu { impl Cpu {
/// Fetch an [Instruction] from the memory bus /// Fetch an [Instruction] from the memory bus
/// (4 cycles) /// (4 cycles)
@@ -151,16 +141,6 @@ impl BusIo for Cpu {
} }
impl Cpu { impl Cpu {
#[inline]
pub(crate) fn bus(&self) -> &Bus {
&self.bus
}
#[inline]
pub(crate) fn bus_mut(&mut self) -> &mut Bus {
&mut self.bus
}
fn handle_ei(&mut self) { fn handle_ei(&mut self) {
match self.ime { match self.ime {
ImeState::EiExecuted => self.ime = ImeState::Pending, ImeState::EiExecuted => self.ime = ImeState::Pending,
@@ -258,12 +238,6 @@ enum State {
// 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::*;
@@ -457,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() {
@@ -518,8 +486,38 @@ pub(crate) enum ImeState {
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)
} }
} }

View File

@@ -1,7 +1,7 @@
use crate::apu::gen::SampleProducer; use crate::apu::gen::SampleProducer;
use crate::bus::BOOT_SIZE; use crate::bus::BOOT_SIZE;
use crate::cartridge::Cartridge;
use crate::cpu::Cpu; use crate::cpu::Cpu;
use crate::joypad::{self, Joypad};
use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
use clap::crate_name; use clap::crate_name;
use gilrs::Gilrs; use gilrs::Gilrs;
@@ -9,56 +9,128 @@ use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; 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 = 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";
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle { #[inline]
pub fn run_frame(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput) {
run(cpu, gamepad, key, CYCLES_IN_FRAME)
}
#[inline]
pub fn run(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput, cycles: Cycle) {
let mut elapsed = 0; let mut elapsed = 0;
if let Some(event) = gamepad.next_event() { if let Some(event) = gamepad.next_event() {
joypad::handle_gamepad_input(emu.joyp_mut(), event); crate::joypad::handle_gamepad_input(&mut cpu.bus.joyp, event);
} }
joypad::handle_keyboard_input(emu.joyp_mut(), key); crate::joypad::handle_keyboard_input(&mut cpu.bus.joyp, key);
while elapsed < CYCLES_IN_FRAME { while elapsed < cycles {
elapsed += emu.step(); elapsed += cpu.step();
}
elapsed
}
pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) {
buf.copy_from_slice(emu.cpu.bus().ppu().frame_buf());
}
pub struct Emulator {
cpu: Cpu,
timestamp: Cycle,
}
impl Default for Emulator {
fn default() -> Self {
Self::new()
} }
} }
impl Emulator { pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) {
pub fn new() -> Self { write_save(cpu);
Self { *control_flow = ControlFlow::Exit;
cpu: Cpu::with_boot(*include_bytes!("../bin/bootix_dmg.bin")), }
timestamp: Default::default(),
#[inline]
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 from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Self> { pub fn load_save(cpu: &mut Cpu) {
Ok(Self { match cpu.bus.cart.as_mut() {
cpu: Cpu::with_boot(Self::read_boot(path)?), Some(cart) => match read_save_from_file(cart) {
timestamp: Default::default(), 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]> { fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> {
@@ -69,69 +141,6 @@ impl Emulator {
Ok(buf) Ok(buf)
} }
fn step(&mut self) -> Cycle {
let cycles = self.cpu.step();
self.timestamp += cycles;
cycles
}
pub fn read_game_rom<P: AsRef<Path>>(&mut self, path: P) -> std::io::Result<()> {
self.load_rom(std::fs::read(path.as_ref())?);
Ok(())
}
fn load_rom(&mut self, rom: Vec<u8>) {
self.cpu.bus_mut().load_cart(rom);
}
#[inline]
fn joyp_mut(&mut self) -> &mut Joypad {
self.cpu.bus_mut().joyp_mut()
}
pub fn set_prod(&mut self, prod: SampleProducer<f32>) {
self.cpu.bus_mut().apu_mut().attach_producer(prod)
}
pub fn title(&self) -> &str {
self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE)
}
pub fn try_write_sav(&self) -> std::io::Result<()> {
if let Some(ext_ram) = self.cpu.bus().cart().map(|c| c.ext_ram()).flatten() {
if let Some(title) = self.cpu.bus().cart_title() {
let mut save_path = Self::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(ext_ram)?;
}
}
Ok(())
}
pub fn try_load_sav(&mut self) -> std::io::Result<()> {
if let Some(cart) = self.cpu.bus_mut().cart_mut() {
if let Some(title) = cart.title() {
let mut save_path = Self::data_path().unwrap_or_else(|| PathBuf::from("."));
save_path.push(title);
save_path.set_extension("sav");
if let Ok(mut file) = File::open(&save_path) {
tracing::info!("Load {:?}", save_path);
let mut memory = Vec::new();
file.read_to_end(&mut memory)?;
cart.write_ext_ram(memory);
}
}
}
Ok(())
}
fn data_path() -> Option<PathBuf> { fn data_path() -> Option<PathBuf> {
match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) { match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) {
Some(dirs) => { Some(dirs) => {
@@ -142,4 +151,13 @@ impl Emulator {
None => None, 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,
} }

472
src/gui.rs Normal file
View File

@@ -0,0 +1,472 @@
use egui::{Context, TextureId};
use egui_wgpu_backend::{wgpu, RenderPass, ScreenDescriptor};
use egui_winit_platform::Platform;
use wgpu::{
Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
Surface, SurfaceCapabilities, SurfaceConfiguration, Texture, TextureView,
TextureViewDescriptor,
};
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 {
should_quit: Default::default(),
title,
mode: EmuMode::Running,
window,
platform,
surface,
device,
queue,
surface_config,
render_pass,
texture,
texture_size,
texture_id,
}
}
pub fn maybe_quit(&self, cpu: &Cpu, control_flow: &mut ControlFlow) {
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);
}
pub fn update_time(&mut self, elapsed_seconds: f64) {
self.platform.update_time(elapsed_seconds);
}
pub fn resize(&mut self, size: PhysicalSize<u32>) {
// See: https://github.com/rust-windowing/winit/issues/208
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);
}
}
pub fn paint(&mut self, cpu: &Cpu) {
use wgpu::{Color, SurfaceError};
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.draw_egui(cpu, &self.platform.context(), self.texture_id);
// End the UI frame. We could now handle the output and draw the UI with the backend.
let full_output = self.platform.end_frame(Some(&self.window));
let paint_jobs = self.platform.context().tessellate(full_output.shapes);
let mut encoder = create_command_encoder(&self.device);
let screen_descriptor = create_screen_descriptor(&self.window, &self.surface_config);
let tdelta = full_output.textures_delta;
// Upload all resources for the GPU.
self.render_pass
.add_textures(&self.device, &self.queue, &tdelta)
.expect("add texture ok");
self.render_pass
.update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor);
// Record all render passes.
self.render_pass
.execute(
&mut encoder,
&output_view,
&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,
))
}
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)
}
#[inline]
fn create_command_encoder(device: &Device) -> CommandEncoder {
use wgpu::CommandEncoderDescriptor;
device.create_command_encoder(&CommandEncoderDescriptor {
label: Some("encoder"),
})
}
#[inline]
fn create_screen_descriptor(window: &Window, config: &SurfaceConfiguration) -> ScreenDescriptor {
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))
}
}

View File

@@ -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;
}
} }

View File

@@ -1,6 +1,6 @@
use self::add::{Source as AddSource, Target as AddTarget}; use self::add::{Source as AddSource, Target as AddTarget};
use self::alu::Source as AluSource; use self::alu::Source as AluSource;
use self::jump::{JumpCondition, JumpLocation}; use self::jump::{JpCond, JpLoc};
use self::load::{Source as LDSource, Target as LDTarget}; use self::load::{Source as LDSource, Target as LDTarget};
use self::table::{ use self::table::{
alu_imm_instr, alu_reg_instr, flag_instr, group1, group2, group3, jump_cond, prefix_alu, alu_imm_instr, alu_reg_instr, flag_instr, group1, group2, group3, jump_cond, prefix_alu,
@@ -16,7 +16,7 @@ use crate::Cycle;
pub(crate) enum Instruction { pub(crate) enum Instruction {
NOP, NOP,
STOP, STOP,
JR(JumpCondition), JR(JpCond),
LD(LDTarget, LDSource), LD(LDTarget, LDSource),
ADD(AddTarget, AddSource), ADD(AddTarget, AddSource),
LDHL, LDHL,
@@ -38,13 +38,13 @@ pub(crate) enum Instruction {
XOR(AluSource), XOR(AluSource),
OR(AluSource), OR(AluSource),
CP(AluSource), CP(AluSource),
RET(JumpCondition), RET(JpCond),
POP(Group3RegisterPair), POP(Group3RegisterPair),
RETI, RETI,
JP(JumpCondition, JumpLocation), JP(JpCond, JpLoc),
DI, DI,
EI, EI,
CALL(JumpCondition), CALL(JpCond),
PUSH(Group3RegisterPair), PUSH(Group3RegisterPair),
RST(u8), RST(u8),
RLC(Register), RLC(Register),
@@ -58,6 +58,7 @@ pub(crate) enum Instruction {
BIT(u8, Register), BIT(u8, Register),
RES(u8, Register), RES(u8, Register),
SET(u8, Register), SET(u8, Register),
Invalid,
} }
impl std::fmt::Debug for Instruction { impl std::fmt::Debug for Instruction {
@@ -109,6 +110,7 @@ impl std::fmt::Debug for Instruction {
BIT(b, r) => write!(f, "BIT {}, {:?}", b, r), BIT(b, r) => write!(f, "BIT {}, {:?}", b, r),
RES(b, r) => write!(f, "RES {}, {:?}", b, r), RES(b, r) => write!(f, "RES {}, {:?}", b, r),
SET(b, r) => write!(f, "SET {}, {:?}", b, r), SET(b, r) => write!(f, "SET {}, {:?}", b, r),
Invalid => f.write_str("???"),
} }
} }
} }
@@ -293,7 +295,7 @@ impl Instruction {
let addr = pc.wrapping_add(byte as u16); let addr = pc.wrapping_add(byte as u16);
match cond { match cond {
JumpCondition::NotZero => { JpCond::NotZero => {
if !flags.z() { if !flags.z() {
Self::jump(cpu, addr); Self::jump(cpu, addr);
12 12
@@ -301,7 +303,7 @@ impl Instruction {
8 8
} }
} }
JumpCondition::Zero => { JpCond::Zero => {
if flags.z() { if flags.z() {
Self::jump(cpu, addr); Self::jump(cpu, addr);
12 12
@@ -309,7 +311,7 @@ impl Instruction {
8 8
} }
} }
JumpCondition::NotCarry => { JpCond::NotCarry => {
if !flags.c() { if !flags.c() {
Self::jump(cpu, addr); Self::jump(cpu, addr);
12 12
@@ -317,7 +319,7 @@ impl Instruction {
8 8
} }
} }
JumpCondition::Carry => { JpCond::Carry => {
if flags.c() { if flags.c() {
Self::jump(cpu, addr); Self::jump(cpu, addr);
12 12
@@ -325,7 +327,7 @@ impl Instruction {
8 8
} }
} }
JumpCondition::Always => { JpCond::Always => {
Self::jump(cpu, addr); Self::jump(cpu, addr);
12 12
} }
@@ -837,7 +839,7 @@ impl Instruction {
let flags: Flags = *cpu.flags(); let flags: Flags = *cpu.flags();
match cond { match cond {
JumpCondition::NotZero => { JpCond::NotZero => {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
if !flags.z() { if !flags.z() {
@@ -848,7 +850,7 @@ impl Instruction {
8 8
} }
} }
JumpCondition::Zero => { JpCond::Zero => {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
if flags.z() { if flags.z() {
@@ -859,7 +861,7 @@ impl Instruction {
8 8
} }
} }
JumpCondition::NotCarry => { JpCond::NotCarry => {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
if !flags.c() { if !flags.c() {
@@ -870,7 +872,7 @@ impl Instruction {
8 8
} }
} }
JumpCondition::Carry => { JpCond::Carry => {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
if flags.c() { if flags.c() {
@@ -881,7 +883,7 @@ impl Instruction {
8 8
} }
} }
JumpCondition::Always => { JpCond::Always => {
let addr = Self::pop(cpu); let addr = Self::pop(cpu);
Self::jump(cpu, addr); Self::jump(cpu, addr);
16 16
@@ -908,13 +910,13 @@ impl Instruction {
16 16
} }
Instruction::JP(cond, location) => match location { Instruction::JP(cond, location) => match location {
JumpLocation::HL => { JpLoc::HL => {
// JP HL | Store HL in program counter // JP HL | Store HL in program counter
let right = cpu.register_pair(RegisterPair::HL); let right = cpu.register_pair(RegisterPair::HL);
cpu.set_register_pair(RegisterPair::PC, right); cpu.set_register_pair(RegisterPair::PC, right);
4 4
} }
JumpLocation::ImmediateWord => { JpLoc::ImmediateWord => {
// JP cond u16 | Store u16 in program counter if condition is true // JP cond u16 | Store u16 in program counter if condition is true
// JP u16 | Store u16 in program counter // JP u16 | Store u16 in program counter
let flags: Flags = *cpu.flags(); let flags: Flags = *cpu.flags();
@@ -922,7 +924,7 @@ impl Instruction {
let addr = Self::imm_word(cpu); let addr = Self::imm_word(cpu);
match cond { match cond {
JumpCondition::NotZero => { JpCond::NotZero => {
if !flags.z() { if !flags.z() {
Self::jump(cpu, addr); Self::jump(cpu, addr);
16 16
@@ -930,7 +932,7 @@ impl Instruction {
12 12
} }
} }
JumpCondition::Zero => { JpCond::Zero => {
if flags.z() { if flags.z() {
Self::jump(cpu, addr); Self::jump(cpu, addr);
16 16
@@ -938,7 +940,7 @@ impl Instruction {
12 12
} }
} }
JumpCondition::NotCarry => { JpCond::NotCarry => {
if !flags.c() { if !flags.c() {
Self::jump(cpu, addr); Self::jump(cpu, addr);
16 16
@@ -946,7 +948,7 @@ impl Instruction {
12 12
} }
} }
JumpCondition::Carry => { JpCond::Carry => {
if flags.c() { if flags.c() {
Self::jump(cpu, addr); Self::jump(cpu, addr);
16 16
@@ -954,7 +956,7 @@ impl Instruction {
12 12
} }
} }
JumpCondition::Always => { JpCond::Always => {
Self::jump(cpu, addr); Self::jump(cpu, addr);
16 16
} }
@@ -980,7 +982,7 @@ impl Instruction {
let return_addr = cpu.register_pair(RegisterPair::PC); let return_addr = cpu.register_pair(RegisterPair::PC);
match cond { match cond {
JumpCondition::NotZero => { JpCond::NotZero => {
if !flags.z() { if !flags.z() {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
Self::push(cpu, return_addr); Self::push(cpu, return_addr);
@@ -990,7 +992,7 @@ impl Instruction {
12 12
} }
} }
JumpCondition::Zero => { JpCond::Zero => {
if flags.z() { if flags.z() {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
Self::push(cpu, return_addr); Self::push(cpu, return_addr);
@@ -1000,7 +1002,7 @@ impl Instruction {
12 12
} }
} }
JumpCondition::NotCarry => { JpCond::NotCarry => {
if !flags.c() { if !flags.c() {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
Self::push(cpu, return_addr); Self::push(cpu, return_addr);
@@ -1010,7 +1012,7 @@ impl Instruction {
12 12
} }
} }
JumpCondition::Carry => { JpCond::Carry => {
if flags.c() { if flags.c() {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
Self::push(cpu, return_addr); Self::push(cpu, return_addr);
@@ -1020,7 +1022,7 @@ impl Instruction {
12 12
} }
} }
JumpCondition::Always => { JpCond::Always => {
cpu.bus.clock(); // internal branch decision cpu.bus.clock(); // internal branch decision
Self::push(cpu, return_addr); Self::push(cpu, return_addr);
cpu.set_register_pair(RegisterPair::PC, addr); cpu.set_register_pair(RegisterPair::PC, addr);
@@ -1294,6 +1296,7 @@ impl Instruction {
} }
} }
} }
Instruction::Invalid => panic!("Attempted to execute invalid instruction"),
} }
} }
@@ -1557,7 +1560,7 @@ impl Instruction {
// STOP // STOP
0o020 => STOP, 0o020 => STOP,
// JR i8 // JR i8
0o030 => JR(JumpCondition::Always), 0o030 => JR(JpCond::Always),
// JR cond i8 // JR cond i8
0o040 | 0o050 | 0o060 | 0o070 => JR(jump_cond((byte >> 3) & 0x03)), 0o040 | 0o050 | 0o060 | 0o070 => JR(jump_cond((byte >> 3) & 0x03)),
// LD r16, u16 // LD r16, u16
@@ -1622,16 +1625,16 @@ impl Instruction {
// POP r16 // POP r16
0o301 | 0o321 | 0o341 | 0o361 => POP(group3((byte >> 4) & 0x03)), 0o301 | 0o321 | 0o341 | 0o361 => POP(group3((byte >> 4) & 0x03)),
// RET // RET
0o311 => RET(JumpCondition::Always), 0o311 => RET(JpCond::Always),
// RETI // RETI
0o331 => RETI, 0o331 => RETI,
// JP HL // JP HL
0o351 => JP(JumpCondition::Always, JumpLocation::HL), 0o351 => JP(JpCond::Always, JpLoc::HL),
// LD SP, HL // LD SP, HL
0o371 => LD(LDTarget::SP, LDSource::HL), 0o371 => LD(LDTarget::SP, LDSource::HL),
// JP cond u16 // JP cond u16
0o302 | 0o312 | 0o322 | 0o332 => { 0o302 | 0o312 | 0o322 | 0o332 => {
JP(jump_cond((byte >> 3) & 0x03), JumpLocation::ImmediateWord) JP(jump_cond((byte >> 3) & 0x03), JpLoc::ImmediateWord)
} }
// LD (0xFF00 + C), A // LD (0xFF00 + C), A
0o342 => LD(LDTarget::IoWithC, LDSource::A), 0o342 => LD(LDTarget::IoWithC, LDSource::A),
@@ -1642,9 +1645,7 @@ impl Instruction {
// LD A, (u16) // LD A, (u16)
0o372 => LD(LDTarget::A, LDSource::IndirectImmediateWord), 0o372 => LD(LDTarget::A, LDSource::IndirectImmediateWord),
// JP u16 // JP u16
0o303 => JP(JumpCondition::Always, JumpLocation::ImmediateWord), 0o303 => JP(JpCond::Always, JpLoc::ImmediateWord),
// 0xCB Prefix
0o313 => unreachable!("{:#04X} should be handled by the prefixed decoder", byte),
// DI // DI
0o363 => DI, 0o363 => DI,
// EI // EI
@@ -1653,12 +1654,14 @@ impl Instruction {
0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)), 0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)),
// PUSH r16 // PUSH r16
0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)), 0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)),
0o315 => CALL(JumpCondition::Always), // CALL u16
0o315 => CALL(JpCond::Always),
// ADD, ADC, SUB, SBC, AND, XOR, OR, and CP
0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => { 0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => {
alu_imm_instr((byte >> 3) & 0x07) alu_imm_instr((byte >> 3) & 0x07)
} }
0o307 | 0o317 | 0o327 | 0o337 | 0o347 | 0o357 | 0o367 | 0o377 => RST(byte & 0b00111000), 0o307 | 0o317 | 0o327 | 0o337 | 0o347 | 0o357 | 0o367 | 0o377 => RST(byte & 0b00111000),
_ => panic!("{:#04X} is an illegal opcode", byte), _ => Invalid, // 0xCB is 0o313
} }
} }
@@ -1681,7 +1684,7 @@ impl Instruction {
mod jump { mod jump {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(crate) enum JumpCondition { pub(crate) enum JpCond {
Always, Always,
NotZero, NotZero,
Zero, Zero,
@@ -1689,9 +1692,9 @@ mod jump {
Carry, Carry,
} }
impl std::fmt::Debug for JumpCondition { impl std::fmt::Debug for JpCond {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use JumpCondition::*; use JpCond::*;
match self { match self {
Always => f.write_str(""), Always => f.write_str(""),
@@ -1704,14 +1707,14 @@ mod jump {
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub(crate) enum JumpLocation { pub(crate) enum JpLoc {
HL, HL,
ImmediateWord, ImmediateWord,
} }
impl std::fmt::Debug for JumpLocation { impl std::fmt::Debug for JpLoc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use JumpLocation::*; use JpLoc::*;
match *self { match *self {
HL => f.write_str("HL"), HL => f.write_str("HL"),
@@ -1871,7 +1874,7 @@ mod load {
mod table { mod table {
use super::add::{Source as AddSource, Target as AddTarget}; use super::add::{Source as AddSource, Target as AddTarget};
use super::alu::Source as AluSource; use super::alu::Source as AluSource;
use super::{Instruction, JumpCondition}; use super::{Instruction, JpCond};
use crate::cpu::{Register as CpuRegister, RegisterPair}; use crate::cpu::{Register as CpuRegister, RegisterPair};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@@ -2070,12 +2073,12 @@ mod table {
0b101 => L, 0b101 => L,
0b110 => IndirectHL, 0b110 => IndirectHL,
0b111 => A, 0b111 => A,
_ => unreachable!("{:#04X} is not a valid Register"), _ => unreachable!("{:#04X} is not a valid Register", code),
} }
} }
pub(crate) fn jump_cond(code: u8) -> JumpCondition { pub(crate) fn jump_cond(code: u8) -> JpCond {
use JumpCondition::*; use JpCond::*;
match code { match code {
0b00 => NotZero, 0b00 => NotZero,
@@ -2150,3 +2153,268 @@ mod table {
} }
} }
} }
pub(crate) mod dbg {
use std::borrow::Cow;
use super::add::{Source as AddSource, Target as AddTarget};
use super::alu::Source as AluSource;
use super::jump::{JpCond, JpLoc};
use super::load::{Source as LDSource, Target as LDTarget};
use super::{AllRegisters, BusIo, Cpu, Instruction, RegisterPair};
pub(crate) fn tmp_disasm(cpu: &Cpu, limit: u8) -> String {
let mut sm83_asm = String::new();
let mut pc = cpu.register_pair(RegisterPair::PC);
for _ in 0..limit {
let opcode = cpu.read_byte(pc);
pc += 1;
let maybe_instr = if opcode == 0xCB {
let opcode = cpu.read_byte(pc);
pc += 1;
Instruction::prefixed(opcode)
} else {
Instruction::unprefixed(opcode)
};
match maybe_instr {
Instruction::Invalid => {}
instr => {
let asm = format!("{:04X} {:?}\n", pc - 1, instr);
sm83_asm.push_str(&asm);
pc += delta::pc_inc_count(instr)
}
}
}
sm83_asm
}
pub(crate) fn new_disasm(cpu: &Cpu, limit: u8) -> String {
let mut assembly = String::new();
let mut pc = cpu.register_pair(RegisterPair::PC);
for _ in 0..limit {
let opcode = cpu.read_byte(pc);
pc += 1;
let maybe_instr = if opcode == 0xCB {
let opcode = cpu.read_byte(pc);
pc += 1;
Instruction::prefixed(opcode)
} else {
Instruction::unprefixed(opcode)
};
match maybe_instr {
Instruction::Invalid => {}
instr => {
let output = format!("${:04X} {}\n", pc - 1, disasm(cpu, pc, instr));
assembly.push_str(&output);
pc += delta::pc_inc_count(instr);
}
}
}
assembly
}
// TODO: It might be better if I pass in a mutable writer instead of usnig a String
fn disasm(cpu: &Cpu, pc: u16, instr: Instruction) -> Cow<str> {
use Instruction::*;
let imm_byte = cpu.read_byte(pc + 1);
let imm_word = (cpu.read_byte(pc + 2) as u16) << 8 | imm_byte as u16;
match instr {
// Unprefixed Instructions
NOP => "NOP".into(),
LD(LDTarget::IndirectImmediateWord, LDSource::SP) => {
format!("LD ({:#06X}), SP", imm_word).into()
}
STOP => "STOP".into(),
JR(JpCond::Always) => format!("JR {}", imm_byte as i8).into(),
JR(cond) => format!("JR {:?} {}", cond, imm_byte as i8).into(),
LD(LDTarget::Group1(rp), LDSource::ImmediateWord) => {
format!("LD {:?} {:#06X}", rp, imm_word).into()
}
ADD(AddTarget::HL, AddSource::Group1(rp)) => format!("ADD HL, {:?}", rp).into(),
LD(LDTarget::IndirectGroup2(rp), LDSource::A) => format!("LD ({:?}), A", rp).into(),
LD(LDTarget::A, LDSource::IndirectGroup2(rp)) => format!("LD A, ({:?})", rp).into(),
INC(AllRegisters::Group1(rp)) => format!("INC {:?}", rp).into(),
DEC(AllRegisters::Group1(rp)) => format!("DEC {:?}", rp).into(),
INC(AllRegisters::Register(reg)) => format!("INC {:?}", reg).into(),
DEC(AllRegisters::Register(reg)) => format!("DEC {:?}", reg).into(),
LD(LDTarget::Register(reg), LDSource::ImmediateByte) => {
format!("LD {:?}, {:#04X}", reg, imm_byte).into()
}
RLCA => "RLCA".into(),
RRCA => "RRCA".into(),
RLA => "RLA".into(),
RRA => "RRA".into(),
DAA => "DAA".into(),
CPL => "CPL".into(),
SCF => "SCF".into(),
CCF => "CCF".into(),
HALT => "HALT".into(),
LD(LDTarget::Register(left), LDSource::Register(right)) => {
format!("LD {:?}, {:?}", left, right).into()
}
ADD(AddTarget::A, AddSource::Register(reg)) => format!("ADD A, {:?}", reg).into(),
ADC(AluSource::Register(reg)) => format!("ADC {:?}", reg).into(),
SUB(AluSource::Register(reg)) => format!("SUB {:?}", reg).into(),
SBC(AluSource::Register(reg)) => format!("SBC {:?}", reg).into(),
AND(AluSource::Register(reg)) => format!("AND {:?}", reg).into(),
XOR(AluSource::Register(reg)) => format!("XOR {:?}", reg).into(),
OR(AluSource::Register(reg)) => format!("OR {:?}", reg).into(),
CP(AluSource::Register(reg)) => format!("CP {:?}", reg).into(),
RET(JpCond::Always) => "RET".into(),
RET(cond) => format!("RET {:?}", cond).into(),
LD(LDTarget::IoWithImmediateOffset, LDSource::A) => {
format!("LD ({:#06X}) , A", 0xFF00 + imm_byte as u16).into()
}
ADD(AddTarget::SP, AddSource::ImmediateSignedByte) => {
format!("ADD SP, {}", imm_byte as i8).into()
}
LD(LDTarget::A, LDSource::IoWithImmediateOffset) => {
format!("LD A, ({:#06X})", 0xFF00 + imm_byte as u16).into()
}
LDHL => format!("LD HL, SP + {}", imm_byte as i8).into(),
POP(rp) => format!("POP {:?}", rp).into(),
RETI => "RETI".into(),
JP(JpCond::Always, JpLoc::HL) => "JP HL".into(),
LD(LDTarget::SP, LDSource::HL) => "LD SP, HL".into(),
JP(JpCond::Always, JpLoc::ImmediateWord) => format!("JP {:#06X}", imm_word).into(),
JP(cond, JpLoc::ImmediateWord) => format!("JP {:?} {:#06X}", cond, imm_word).into(),
LD(LDTarget::IoWithC, LDSource::A) => "LD (0xFF00 + C), A".into(),
LD(LDTarget::IndirectImmediateWord, LDSource::A) => {
format!("LD ({:#06X}), A", imm_byte).into()
}
LD(LDTarget::A, LDSource::IoWithC) => "LD A, (0xFF00 + C)".into(),
LD(LDTarget::A, LDSource::IndirectImmediateWord) => {
format!("LD A, ({:#06X})", imm_word).into()
}
DI => "DI".into(),
EI => "EI".into(),
CALL(cond) => format!("CALL {:?} {:#06X}", cond, imm_word).into(),
PUSH(rp) => format!("PUSH {:?}", rp).into(),
ADD(AddTarget::A, AddSource::ImmediateByte) => {
format!("ADD A, {:#04X}", imm_byte).into()
}
ADC(AluSource::ImmediateByte) => format!("ADC {:#04X}", imm_byte).into(),
SUB(AluSource::ImmediateByte) => format!("SUB {:#04X}", imm_byte).into(),
SBC(AluSource::ImmediateByte) => format!("SBC {:#04X}", imm_byte).into(),
AND(AluSource::ImmediateByte) => format!("AND {:#04X}", imm_byte).into(),
XOR(AluSource::ImmediateByte) => format!("XOR {:#04X}", imm_byte).into(),
OR(AluSource::ImmediateByte) => format!("OR {:#04X}", imm_byte).into(),
CP(AluSource::ImmediateByte) => format!("CP {:#04X}", imm_byte).into(),
RST(v) => format!("RST {:#04X}", v).into(),
// Prefixed Instructions
RLC(reg) => format!("RLC {:?}", reg).into(),
RRC(reg) => format!("RRC {:?}", reg).into(),
RL(reg) => format!("RL {:?}", reg).into(),
RR(reg) => format!("RR {:?}", reg).into(),
SLA(reg) => format!("SLA {:?}", reg).into(),
SRA(reg) => format!("SRA {:?}", reg).into(),
SWAP(reg) => format!("SWAP {:?}", reg).into(),
SRL(reg) => format!("SRL {:?}", reg).into(),
BIT(bit, reg) => format!("BIT {}, {:?}", bit, reg).into(),
RES(bit, reg) => format!("RES {}, {:?}", bit, reg).into(),
SET(bit, reg) => format!("SET {}, {:?}", bit, reg).into(),
_ => unreachable!("{:?} is an illegal instruction", instr),
}
}
mod delta {
use super::super::add::{Source as AddSource, Target as AddTarget};
use super::super::alu::Source as AluSource;
use super::super::jump::{JpCond, JpLoc};
use super::super::load::{Source as LDSource, Target as LDTarget};
use super::super::{AllRegisters, Instruction};
pub(super) fn pc_inc_count(instr: Instruction) -> u16 {
use Instruction::*;
match instr {
// Unprefixed
NOP => 0,
LD(LDTarget::IndirectImmediateWord, LDSource::SP) => 2,
STOP => 0,
JR(_) => 1,
LD(LDTarget::Group1(_), LDSource::ImmediateWord) => 2,
ADD(AddTarget::HL, AddSource::Group1(_)) => 0,
LD(LDTarget::IndirectGroup2(_), LDSource::A) => 0,
LD(LDTarget::A, LDSource::IndirectGroup2(_)) => 0,
INC(AllRegisters::Group1(_)) => 0,
DEC(AllRegisters::Group1(_)) => 0,
INC(AllRegisters::Register(_)) => 0,
DEC(AllRegisters::Register(_)) => 0,
LD(LDTarget::Register(_), LDSource::ImmediateByte) => 1,
RLCA => 0,
RRCA => 0,
RLA => 0,
RRA => 0,
DAA => 0,
CPL => 0,
SCF => 0,
CCF => 0,
HALT => 0,
LD(LDTarget::Register(_), LDSource::Register(_)) => 0,
ADD(AddTarget::A, AddSource::Register(_)) => 0,
ADC(AluSource::Register(_)) => 0,
SUB(AluSource::Register(_)) => 0,
SBC(AluSource::Register(_)) => 0,
AND(AluSource::Register(_)) => 0,
XOR(AluSource::Register(_)) => 0,
OR(AluSource::Register(_)) => 0,
CP(AluSource::Register(_)) => 0,
RET(_) => 0,
LD(LDTarget::IoWithImmediateOffset, LDSource::A) => 1,
ADD(AddTarget::SP, AddSource::ImmediateSignedByte) => 1,
LD(LDTarget::A, LDSource::IoWithImmediateOffset) => 1,
LDHL => 1,
POP(_) => 0,
RETI => 0,
JP(JpCond::Always, JpLoc::HL) => 0,
LD(LDTarget::SP, LDSource::HL) => 0,
JP(_, JpLoc::ImmediateWord) => 2,
LD(LDTarget::IoWithC, LDSource::A) => 0,
LD(LDTarget::IndirectImmediateWord, LDSource::A) => 2,
LD(LDTarget::A, LDSource::IoWithC) => 0,
LD(LDTarget::A, LDSource::IndirectImmediateWord) => 2,
DI => 0,
EI => 0,
CALL(_) => 0,
PUSH(_) => 0,
ADD(AddTarget::A, AddSource::ImmediateByte) => 1,
ADC(AluSource::ImmediateByte) => 1,
SUB(AluSource::ImmediateByte) => 1,
SBC(AluSource::ImmediateByte) => 1,
AND(AluSource::ImmediateByte) => 1,
XOR(AluSource::ImmediateByte) => 1,
OR(AluSource::ImmediateByte) => 1,
CP(AluSource::ImmediateByte) => 1,
RST(_) => 0,
// Prefixed
RLC(_) => 0,
RRC(_) => 0,
RL(_) => 0,
RR(_) => 0,
SLA(_) => 0,
SRA(_) => 0,
SWAP(_) => 0,
SRL(_) => 0,
BIT(_, _) => 0,
RES(_, _) => 0,
SET(_, _) => 0,
_ => unreachable!("{:?} is an illegal instruction", instr),
}
}
}
}

View File

@@ -1,11 +1,20 @@
use bitfield::bitfield; use bitfield::bitfield;
#[derive(Debug, 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)

View File

@@ -1,5 +1,5 @@
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)] #[derive(Debug)]
pub struct Joypad { pub struct Joypad {
@@ -111,68 +111,33 @@ impl ButtonEvent {
} }
#[inline] #[inline]
pub(crate) fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) { pub(crate) fn handle_keyboard_input(pad: &mut Joypad, key: KeyboardInput) {
use winit::event::VirtualKeyCode;
// 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 {
} Some(VirtualKeyCode::Down) => state.dpad_down.update(true, irq),
if input.key_released(VirtualKeyCode::Down) { Some(VirtualKeyCode::Up) => state.dpad_up.update(true, irq),
state.dpad_down.update(false, irq); Some(VirtualKeyCode::Left) => state.dpad_left.update(true, irq),
} Some(VirtualKeyCode::Right) => state.dpad_right.update(true, irq),
Some(VirtualKeyCode::Return) => state.start.update(true, irq),
if input.key_pressed(VirtualKeyCode::Up) { Some(VirtualKeyCode::RShift) => state.select.update(true, irq),
state.dpad_up.update(true, irq); Some(VirtualKeyCode::Z) => state.south.update(true, irq),
} Some(VirtualKeyCode::X) => state.east.update(true, irq),
if input.key_released(VirtualKeyCode::Up) { None | Some(_) => {}
state.dpad_up.update(false, irq); },
} ElementState::Released => match key.virtual_keycode {
Some(VirtualKeyCode::Down) => state.dpad_down.update(false, irq),
if input.key_pressed(VirtualKeyCode::Left) { Some(VirtualKeyCode::Up) => state.dpad_up.update(false, irq),
state.dpad_left.update(true, irq); Some(VirtualKeyCode::Left) => state.dpad_left.update(false, irq),
} Some(VirtualKeyCode::Right) => state.dpad_right.update(false, irq),
if input.key_released(VirtualKeyCode::Left) { Some(VirtualKeyCode::Return) => state.start.update(false, irq),
state.dpad_left.update(false, irq); Some(VirtualKeyCode::RShift) => state.select.update(false, irq),
} Some(VirtualKeyCode::Z) => state.south.update(false, irq),
Some(VirtualKeyCode::X) => state.east.update(false, irq),
if input.key_pressed(VirtualKeyCode::Right) { None | Some(_) => {}
state.dpad_right.update(true, irq); },
}
if input.key_released(VirtualKeyCode::Right) {
state.dpad_right.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Return) {
state.start.update(true, irq);
}
if input.key_released(VirtualKeyCode::Return) {
state.start.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::RShift) {
state.select.update(true, irq);
}
if input.key_released(VirtualKeyCode::RShift) {
state.select.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Z) {
state.south.update(true, irq);
}
if input.key_released(VirtualKeyCode::Z) {
state.south.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::X) {
state.east.update(true, irq);
}
if input.key_released(VirtualKeyCode::X) {
state.east.update(false, irq);
} }
} }

View File

@@ -1,3 +1,5 @@
#![allow(clippy::derivable_impls)] // Will remove this if bitfield-rs allows default impls
pub use apu::gen::init as spsc_init; pub use apu::gen::init as spsc_init;
pub type Cycle = u64; pub type Cycle = u64;
@@ -9,6 +11,7 @@ mod bus;
mod cartridge; mod cartridge;
mod cpu; mod cpu;
pub mod emu; pub mod emu;
pub mod gui;
mod high_ram; mod high_ram;
mod instruction; mod instruction;
mod interrupt; mod interrupt;

View File

@@ -1,42 +1,28 @@
use anyhow::Result; use std::path::PathBuf;
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; use std::time::Instant;
use gb::emu::{Emulator, CYCLES_IN_FRAME};
use gb::{Cycle, GB_HEIGHT, GB_WIDTH}; use clap::{arg, command, value_parser};
use gilrs::Gilrs; use gb::gui::{EmuMode, Gui};
use pixels::{PixelsBuilder, SurfaceTexture}; use gb::{emu, gui};
use rodio::{OutputStream, Sink}; use gilrs::Gilrs;
use tracing::info; use rodio::{OutputStream, Sink};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use winit::dpi::{LogicalSize, PhysicalSize}; use winit::event::{Event, WindowEvent};
use winit::event::{Event, VirtualKeyCode}; use winit::event_loop::{EventLoop, EventLoopBuilder};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};
use winit_input_helper::WinitInputHelper;
const WINDOW_SCALE: usize = 3;
const AUDIO_ENABLED: bool = true; const AUDIO_ENABLED: bool = true;
fn main() -> Result<()> { fn main() {
let app = App::new(crate_name!()) let m = command!()
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!());
let m = app
.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)),
.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();
@@ -49,115 +35,92 @@ fn main() -> Result<()> {
.with_env_filter(EnvFilter::from_default_env()) .with_env_filter(EnvFilter::from_default_env())
.init(); .init();
let mut emu = match m.value_of("boot") { // Init CPU
let mut cpu = match m.get_one::<PathBuf>("boot") {
Some(path) => { Some(path) => {
tracing::info!("User-provided boot ROM"); tracing::info!("User-provided boot ROM");
Emulator::from_boot_rom(path)? emu::from_boot_rom(path).expect("initialize emulator with custom boot rom")
} }
None => { None => {
tracing::info!("Built-in boot ROM"); tracing::info!("Built-in boot ROM");
Emulator::new() Default::default()
} }
}; };
if let Some(path) = m.value_of("rom") { // Load ROM if filepath was provided
if let Some(path) = m.get_one::<PathBuf>("ROM_FILE") {
tracing::info!("User-provided cartridge ROM"); tracing::info!("User-provided cartridge ROM");
emu.read_game_rom(path)?; 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();
// Load Save file if it exists tracing::info!("Initialize Gamepad");
emu.try_load_sav().expect("Load save if exists");
let rom_title = emu.title();
info!("Initialize Gamepad");
let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
// Initialize GUI
let event_loop = EventLoop::new();
let mut input = WinitInputHelper::new();
let window = create_window(&event_loop, rom_title)?;
let mut pixels = {
let size = window.inner_size();
let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
.enable_vsync(false)
.build()?
};
// Initialize Audio // Initialize Audio
let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio"); let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
if AUDIO_ENABLED { if AUDIO_ENABLED {
let (prod, cons) = gb::spsc_init(); let (prod, cons) = gb::spsc_init();
let sink = { let sink = {
let s = Sink::try_new(&stream_handle)?; let s = Sink::try_new(&stream_handle).expect("create sink from audio stream handle");
s.append(cons); s.append(cons);
s.set_volume(0.1); s.set_volume(0.1);
s s
}; };
emu.set_prod(prod); emu::set_audio_prod(&mut cpu, prod);
info!("Spawn Audio Thread"); tracing::info!("Spawn Audio Thread");
std::thread::spawn(move || { std::thread::spawn(move || {
sink.sleep_until_end(); sink.sleep_until_end();
}); });
} }
let mut cycle_count: Cycle = Default::default(); // Set up state for the Immediate-mode GUI
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| {
if let Event::RedrawRequested(_) = event { app.handle_event(&event);
if pixels.render().is_err() {
emu.try_write_sav().expect("Write game save if need be");
*control_flow = ControlFlow::Exit; match event {
return; Event::MainEventsCleared => {
app.maybe_quit(&cpu, control_flow);
match app.mode {
EmuMode::Running => emu::run_frame(&mut cpu, &mut gamepad, last_key),
EmuMode::StepFrame if gui::kbd::space_released(&last_key) => {
emu::run_frame(&mut cpu, &mut gamepad, last_key)
} }
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();
emu.try_write_sav().expect("Write game save if need be");
*control_flow = ControlFlow::Exit; app.request_redraw();
return;
} }
Event::RedrawRequested(..) => {
if let Some(size) = input.window_resized() { app.update_time(start_time.elapsed().as_secs_f64());
pixels.resize_surface(size.width, size.height); app.paint(&cpu);
}
cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input);
if cycle_count >= CYCLES_IN_FRAME {
cycle_count %= CYCLES_IN_FRAME;
let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels
.get_frame()
.try_into()
.expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4");
gb::emu::draw_frame(&emu, buf);
window.request_redraw();
} }
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 logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64);
let physical = PhysicalSize::new(
(GB_WIDTH * WINDOW_SCALE) as f32,
(GB_HEIGHT * WINDOW_SCALE) as f32,
);
Ok(WindowBuilder::new()
.with_title(title)
.with_min_inner_size(logical)
.with_inner_size(physical)
.with_resizable(true)
.build(event_loop)?)
}

View File

@@ -10,6 +10,8 @@ use types::{
ObjectPaletteKind, ObjectSize, Pixels, RenderPriority, TileDataAddress, ObjectPaletteKind, ObjectSize, Pixels, RenderPriority, TileDataAddress,
}; };
use once_cell::sync::Lazy;
mod dma; mod dma;
mod types; mod types;
@@ -32,6 +34,14 @@ const LIGHT_GRAY: [u8; 4] = 0xAEBA89FFu32.to_be_bytes();
const DARK_GRAY: [u8; 4] = 0x5E6745FFu32.to_be_bytes(); const DARK_GRAY: [u8; 4] = 0x5E6745FFu32.to_be_bytes();
const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes(); const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes();
static BLANK_SCREEN: Lazy<Box<[u8; (GB_WIDTH * 4) * GB_HEIGHT]>> = Lazy::new(|| {
WHITE
.repeat(GB_WIDTH * GB_HEIGHT)
.into_boxed_slice()
.try_into()
.unwrap()
});
#[derive(Debug)] #[derive(Debug)]
pub struct Ppu { pub struct Ppu {
pub(crate) int: Interrupt, pub(crate) int: Interrupt,
@@ -42,13 +52,13 @@ pub struct Ppu {
pub(crate) monochrome: Monochrome, pub(crate) monochrome: Monochrome,
pub(crate) pos: ScreenPosition, pub(crate) pos: ScreenPosition,
vram: Box<[u8; VRAM_SIZE]>, vram: Box<[u8; VRAM_SIZE]>,
pub(crate) oam: ObjectAttributeTable, pub(crate) oam: ObjectAttrTable,
pub(crate) dma: DirectMemoryAccess, pub(crate) dma: DirectMemoryAccess,
scan_dot: Cycle, scan_dot: Cycle,
fetch: PixelFetcher, fetch: PixelFetcher,
fifo: PixelFifo, fifo: PixelFifo,
obj_buffer: ObjectBuffer, obj_buffer: ObjectBuffer,
frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>, pub(crate) frame_buf: FrameBuffer,
win_stat: WindowStatus, win_stat: WindowStatus,
scanline_start: bool, scanline_start: bool,
@@ -70,15 +80,26 @@ impl BusIo for Ppu {
impl Ppu { impl Ppu {
pub(crate) fn tick(&mut self) { pub(crate) fn tick(&mut self) {
self.dot += 1;
if !self.ctrl.lcd_enabled() { if !self.ctrl.lcd_enabled() {
if self.dot > 0 {
// Check ensures this expensive operation only happens once
self.frame_buf
.get_mut(Device::Guest)
.copy_from_slice(BLANK_SCREEN.as_ref());
}
self.stat.set_mode(PpuMode::HBlank);
self.pos.line_y = 0;
self.dot = 0;
return; return;
} }
self.dot += 1;
match self.stat.mode() { match self.stat.mode() {
PpuMode::OamScan => { PpuMode::OamScan => {
// Cycles 1 -> 80 // Cycles 1 -> 80
if self.dot >= 80 { if self.dot >= 80 {
self.x_pos = 0; self.x_pos = 0;
self.scanline_start = true; self.scanline_start = true;
@@ -104,12 +125,7 @@ impl Ppu {
self.scan_oam(); self.scan_oam();
} }
PpuMode::Drawing => { PpuMode::Drawing => {
if self.ctrl.lcd_enabled() {
// Only Draw when the LCD Is Enabled
self.draw(); self.draw();
} else {
self.reset();
}
if self.x_pos == 160 { if self.x_pos == 160 {
if self.stat.hblank_int() { if self.stat.hblank_int() {
@@ -164,6 +180,9 @@ impl Ppu {
self.int.set_lcd_stat(true); self.int.set_lcd_stat(true);
} }
// Screen is done drawing
self.frame_buf.swap();
PpuMode::VBlank PpuMode::VBlank
} else { } else {
if self.stat.oam_int() { if self.stat.oam_int() {
@@ -265,7 +284,7 @@ impl Ppu {
TileLowB => { TileLowB => {
let obj_size = self.ctrl.obj_size(); let obj_size = self.ctrl.obj_size();
let addr = PixelFetcher::get_obj_addr(attr, &self.pos, obj_size); let addr = PixelFetcher::obj_addr(attr, &self.pos, obj_size);
let byte = self.read_byte(addr); let byte = self.read_byte(addr);
self.fetch.obj.tile.with_low(byte); self.fetch.obj.tile.with_low(byte);
@@ -276,7 +295,7 @@ impl Ppu {
TileHighB => { TileHighB => {
let obj_size = self.ctrl.obj_size(); let obj_size = self.ctrl.obj_size();
let addr = PixelFetcher::get_obj_addr(attr, &self.pos, obj_size); let addr = PixelFetcher::obj_addr(attr, &self.pos, obj_size);
let byte = self.read_byte(addr + 1); let byte = self.read_byte(addr + 1);
self.fetch.obj.tile.with_high(byte); self.fetch.obj.tile.with_high(byte);
@@ -397,8 +416,8 @@ impl Ppu {
let x = self.x_pos as usize; let x = self.x_pos as usize;
let i = (GB_WIDTH * 4) * y + (x * 4); let i = (GB_WIDTH * 4) * y + (x * 4);
self.frame_buf[i..(i + rgba.len())].copy_from_slice(&rgba);
self.frame_buf.get_mut(Device::Guest)[i..(i + rgba.len())].copy_from_slice(&rgba);
self.x_pos += 1; self.x_pos += 1;
} }
@@ -415,19 +434,6 @@ impl Ppu {
} }
} }
fn reset(&mut self) {
self.pos.line_y = 0;
self.stat.set_mode(PpuMode::HBlank);
let mut blank = WHITE.repeat(self.frame_buf.len() / 4);
self.frame_buf.swap_with_slice(&mut blank);
}
#[inline]
pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
&self.frame_buf
}
fn clock_fifo(&mut self) -> Option<GrayShade> { fn clock_fifo(&mut self) -> Option<GrayShade> {
use RenderPriority::*; use RenderPriority::*;
@@ -446,7 +452,7 @@ impl Ppu {
fn obj_pixel(&self, obj: ObjPixelProperty) -> GrayShade { fn obj_pixel(&self, obj: ObjPixelProperty) -> GrayShade {
use ObjectPaletteKind::*; use ObjectPaletteKind::*;
assert!(obj.shade_id != 0); assert_ne!(obj.shade_id, 0);
let p0 = &self.monochrome.obj_palette_0; let p0 = &self.monochrome.obj_palette_0;
let p1 = &self.monochrome.obj_palette_1; let p1 = &self.monochrome.obj_palette_1;
@@ -473,12 +479,12 @@ impl Default for Ppu {
Self { Self {
vram: Box::new([0u8; VRAM_SIZE]), vram: Box::new([0u8; VRAM_SIZE]),
dot: Default::default(), dot: Default::default(),
frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]), frame_buf: FrameBuffer::new().expect("create frame buffers"),
int: Default::default(), int: Default::default(),
ctrl: Default::default(), ctrl: LCDControl(0),
monochrome: Default::default(), monochrome: Default::default(),
pos: Default::default(), pos: Default::default(),
stat: Default::default(), stat: LCDStatus(0x80), // bit 7 is always 1
oam: Default::default(), oam: Default::default(),
scan_dot: Default::default(), scan_dot: Default::default(),
fetch: Default::default(), fetch: Default::default(),
@@ -533,7 +539,7 @@ pub(crate) struct ScreenPosition {
pub(crate) window_x: u8, pub(crate) window_x: u8,
} }
#[derive(Debug, Default)] #[derive(Debug)]
pub(crate) struct Monochrome { pub(crate) struct Monochrome {
/// 0xFF47 | BGP - Background Palette Data /// 0xFF47 | BGP - Background Palette Data
pub(crate) bg_palette: BackgroundPalette, pub(crate) bg_palette: BackgroundPalette,
@@ -543,12 +549,22 @@ pub(crate) struct Monochrome {
pub(crate) obj_palette_1: ObjectPalette, pub(crate) obj_palette_1: ObjectPalette,
} }
impl Default for Monochrome {
fn default() -> Self {
Self {
bg_palette: BackgroundPalette(0),
obj_palette_0: ObjectPalette(0),
obj_palette_1: ObjectPalette(0),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ObjectAttributeTable { pub(crate) struct ObjectAttrTable {
buf: Box<[u8; OAM_SIZE]>, buf: Box<[u8; OAM_SIZE]>,
} }
impl BusIo for ObjectAttributeTable { impl BusIo for ObjectAttrTable {
fn read_byte(&self, addr: u16) -> u8 { fn read_byte(&self, addr: u16) -> u8 {
let index = (addr - 0xFE00) as usize; let index = (addr - 0xFE00) as usize;
self.buf[index] self.buf[index]
@@ -560,8 +576,8 @@ impl BusIo for ObjectAttributeTable {
} }
} }
impl ObjectAttributeTable { impl ObjectAttrTable {
fn attribute(&self, index: usize) -> ObjectAttribute { fn attribute(&self, index: usize) -> ObjectAttr {
let start = index * 4; let start = index * 4;
let slice: &[u8; 4] = self.buf[start..(start + 4)] let slice: &[u8; 4] = self.buf[start..(start + 4)]
@@ -572,7 +588,7 @@ impl ObjectAttributeTable {
} }
} }
impl Default for ObjectAttributeTable { impl Default for ObjectAttrTable {
fn default() -> Self { fn default() -> Self {
Self { Self {
buf: Box::new([0; OAM_SIZE]), buf: Box::new([0; OAM_SIZE]),
@@ -580,15 +596,15 @@ impl Default for ObjectAttributeTable {
} }
} }
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy)]
struct ObjectAttribute { struct ObjectAttr {
y: u8, y: u8,
x: u8, x: u8,
tile_index: u8, tile_index: u8,
flags: ObjectFlags, flags: ObjectFlags,
} }
impl From<[u8; 4]> for ObjectAttribute { impl From<[u8; 4]> for ObjectAttr {
fn from(bytes: [u8; 4]) -> Self { fn from(bytes: [u8; 4]) -> Self {
Self { Self {
y: bytes[0], y: bytes[0],
@@ -599,7 +615,7 @@ impl From<[u8; 4]> for ObjectAttribute {
} }
} }
impl<'a> From<&'a [u8; 4]> for ObjectAttribute { impl<'a> From<&'a [u8; 4]> for ObjectAttr {
fn from(bytes: &'a [u8; 4]) -> Self { fn from(bytes: &'a [u8; 4]) -> Self {
Self { Self {
y: bytes[0], y: bytes[0],
@@ -612,7 +628,7 @@ impl<'a> From<&'a [u8; 4]> for ObjectAttribute {
#[derive(Debug)] #[derive(Debug)]
struct ObjectBuffer { struct ObjectBuffer {
inner: [Option<ObjectAttribute>; OBJECT_LIMIT], inner: [Option<ObjectAttr>; OBJECT_LIMIT],
len: usize, len: usize,
} }
@@ -626,7 +642,7 @@ impl ObjectBuffer {
self.len = 0; self.len = 0;
} }
fn add(&mut self, attr: ObjectAttribute) { fn add(&mut self, attr: ObjectAttr) {
self.inner[self.len] = Some(attr); self.inner[self.len] = Some(attr);
self.len += 1; self.len += 1;
} }
@@ -678,7 +694,7 @@ impl PixelFetcher {
Ok(()) Ok(())
} }
fn get_obj_addr(attr: &ObjectAttribute, pos: &ScreenPosition, size: ObjectSize) -> u16 { fn obj_addr(attr: &ObjectAttr, pos: &ScreenPosition, size: ObjectSize) -> u16 {
let line_y = pos.line_y; let line_y = pos.line_y;
// TODO: Why is the offset 14 and 30 respectively? // TODO: Why is the offset 14 and 30 respectively?
@@ -792,21 +808,12 @@ impl Default for BackgroundFetcher {
} }
} }
#[derive(Debug)] #[derive(Debug, Default)]
struct ObjectFetcher { struct ObjectFetcher {
state: FetcherState, state: FetcherState,
tile: TileBuilder, tile: TileBuilder,
} }
impl Default for ObjectFetcher {
fn default() -> Self {
Self {
state: Default::default(),
tile: Default::default(),
}
}
}
impl Fetcher for ObjectFetcher { impl Fetcher for ObjectFetcher {
fn reset(&mut self) { fn reset(&mut self) {
self.state = Default::default(); self.state = Default::default();
@@ -841,7 +848,7 @@ struct BgPixelProperty {
shade_id: u8, shade_id: u8,
} }
#[derive(Debug, Default)] #[derive(Debug)]
struct ObjPixelProperty { struct ObjPixelProperty {
shade_id: u8, shade_id: u8,
palette_kind: ObjectPaletteKind, palette_kind: ObjectPaletteKind,
@@ -914,3 +921,96 @@ struct WindowStatus {
/// drawing from the window tile map is true /// drawing from the window tile map is true
enabled: bool, enabled: bool,
} }
pub(crate) mod dbg {
use super::{Ppu, PpuMode};
use crate::Cycle;
pub(crate) fn ly(ppu: &Ppu) -> u8 {
ppu.pos.line_y
}
pub(crate) fn scx(ppu: &Ppu) -> u8 {
ppu.pos.scroll_x
}
pub(crate) fn scy(ppu: &Ppu) -> u8 {
ppu.pos.scroll_y
}
pub(crate) fn mode(ppu: &Ppu) -> PpuMode {
ppu.stat.mode()
}
pub(crate) fn wx(ppu: &Ppu) -> i16 {
ppu.pos.window_x as i16
}
pub(crate) fn wy(ppu: &Ppu) -> i16 {
ppu.pos.window_y as i16
}
pub(crate) fn dot(ppu: &Ppu) -> Cycle {
ppu.dot
}
}
#[derive(Debug)]
pub struct FrameBuffer {
buf: [Box<[u8; Self::FRAME_LEN]>; 2],
current: bool,
}
#[derive(PartialEq)]
pub enum Device {
Guest,
Host,
}
impl FrameBuffer {
const FRAME_LEN: usize = GB_WIDTH * std::mem::size_of::<u32>() * GB_HEIGHT;
pub fn new() -> Result<Self, FrameBufferError> {
Ok(Self {
buf: [
vec![0; Self::FRAME_LEN]
.into_boxed_slice()
.try_into()
.map_err(|_| FrameBufferError::TryFrom)?,
vec![0; Self::FRAME_LEN]
.into_boxed_slice()
.try_into()
.map_err(|_| FrameBufferError::TryFrom)?,
],
current: false,
})
}
pub fn swap(&mut self) {
self.current = !self.current;
}
pub fn get_mut(&mut self, device: Device) -> &mut [u8; Self::FRAME_LEN] {
let idx = match device {
Device::Guest => self.current,
Device::Host => !self.current,
};
&mut *self.buf[idx as usize]
}
pub fn get(&self, device: Device) -> &[u8; Self::FRAME_LEN] {
let idx = match device {
Device::Guest => self.current,
Device::Host => !self.current,
};
&*self.buf[idx as usize]
}
}
#[derive(Debug, thiserror::Error)]
pub enum FrameBufferError {
#[error("Failed to coerce boxed slice to boxed array")]
TryFrom,
}

View File

@@ -1,6 +1,6 @@
use crate::Cycle; use crate::Cycle;
#[derive(Debug, Default)] #[derive(Debug)]
pub(crate) struct DirectMemoryAccess { pub(crate) struct DirectMemoryAccess {
pub(crate) state: DmaState, pub(crate) state: DmaState,
cycle: Cycle, cycle: Cycle,
@@ -8,6 +8,16 @@ 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 tick(&mut self) -> Option<(u16, u16)> { pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
match self.state { match self.state {
@@ -69,12 +79,6 @@ pub(crate) enum DmaState {
Transferring, Transferring,
} }
impl Default for DmaState {
fn default() -> Self {
Self::Disabled
}
}
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub(crate) struct DmaAddress(Option<u16>); pub(crate) struct DmaAddress(Option<u16>);

View File

@@ -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,
@@ -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)
@@ -361,24 +313,12 @@ impl From<ObjectFlags> for u8 {
} }
} }
impl Default for ObjectFlags {
fn default() -> Self {
Self(0)
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum ObjectPaletteKind { pub enum ObjectPaletteKind {
Zero = 0, Zero = 0,
One = 1, One = 1,
} }
impl Default for ObjectPaletteKind {
fn default() -> Self {
Self::Zero
}
}
impl From<u8> for ObjectPaletteKind { impl From<u8> for ObjectPaletteKind {
fn from(byte: u8) -> Self { fn from(byte: u8) -> Self {
match byte & 0b01 { match byte & 0b01 {
@@ -395,7 +335,7 @@ impl From<ObjectPaletteKind> for 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,
@@ -417,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,
@@ -442,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 {

View File

@@ -1,79 +0,0 @@
use crate::Cycle;
use std::collections::BinaryHeap;
#[derive(Debug)]
pub(crate) struct Scheduler {
timestamp: Cycle,
queue: BinaryHeap<Event>,
}
impl Scheduler {
pub(crate) fn init() -> Self {
let mut scheduler = Self {
timestamp: Default::default(),
queue: Default::default(),
};
scheduler.push(Event {
kind: EventKind::TimestampOverflow,
timestamp: Cycle::MAX,
cb: |_delay| panic!("Reached Cycle::MAX"),
});
scheduler
}
pub(crate) fn push(&mut self, event: Event) {
self.queue.push(event);
}
pub(crate) fn step(&mut self, cycles: Cycle) {
self.timestamp += cycles;
loop {
let should_pop = match self.queue.peek() {
Some(event) => self.timestamp >= event.timestamp,
None => false,
};
if !should_pop {
break;
}
let event = self.queue.pop().expect("Pop Event from Scheduler Queue");
(event.cb)(self.timestamp - event.timestamp);
}
}
}
#[derive(Debug)]
pub(crate) struct Event {
kind: EventKind,
cb: fn(Cycle),
pub(crate) timestamp: Cycle,
}
impl Eq for Event {}
impl PartialEq for Event {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.timestamp == other.timestamp
}
}
impl PartialOrd for Event {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Event {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.timestamp.cmp(&other.timestamp)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EventKind {
TimestampOverflow,
}

View File

@@ -1,6 +1,6 @@
use bitfield::bitfield; use bitfield::bitfield;
#[derive(Debug, 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 {

View File

@@ -106,7 +106,7 @@ impl Timer {
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,
@@ -157,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)

View File

@@ -11,13 +11,13 @@ pub(crate) struct WorkRam {
} }
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 {
@@ -42,11 +42,11 @@ impl Default for VariableWorkRam {
} }
impl BusIo for VariableWorkRam { impl BusIo for VariableWorkRam {
fn write_byte(&mut self, addr: u16, byte: u8) {
self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
}
fn read_byte(&self, addr: u16) -> u8 { fn read_byte(&self, addr: u16) -> u8 {
self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
} }
fn write_byte(&mut self, addr: u16, byte: u8) {
self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
}
} }