Compare commits

..

4 Commits

Author SHA1 Message Date
9baa15050e in progress refactor
Some checks reported errors
continuous-integration/drone/push Build was killed
Needs profiling
2021-09-17 23:29:55 -03:00
4516ca8477 chore: run cargo fix
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-12 04:56:39 -03:00
6087e3b20b chore: remove Cycle struct and begin scheduler design 2021-09-12 04:56:34 -03:00
10ac579c40 fix(main): Use LogicalSize and PhysicalSize properly 2021-09-11 22:56:40 -03:00
10 changed files with 392 additions and 453 deletions

View File

@@ -7,14 +7,13 @@ use crate::ppu::{Ppu, PpuMode};
use crate::serial::Serial;
use crate::timer::Timer;
use crate::work_ram::{VariableWorkRam, WorkRam};
use std::{fs::File, io::Read};
const BOOT_ROM_SIZE: usize = 0x100;
pub(crate) const BOOT_SIZE: usize = 0x100;
#[derive(Debug)]
pub struct Bus {
boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long
cartridge: Option<Cartridge>,
boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
cart: Option<Cartridge>,
pub(crate) ppu: Ppu,
work_ram: WorkRam,
var_ram: VariableWorkRam,
@@ -30,7 +29,7 @@ impl Default for Bus {
fn default() -> Self {
Self {
boot: None,
cartridge: None,
cart: None,
ppu: Default::default(),
work_ram: Default::default(),
var_ram: Default::default(),
@@ -45,25 +44,19 @@ impl Default for Bus {
}
impl Bus {
pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> {
let mut file = File::open(path)?;
let mut boot_rom = [0u8; 256];
file.read_exact(&mut boot_rom)?;
Ok(Self {
boot: Some(boot_rom),
pub(crate) fn with_boot(rom: [u8; 256]) -> Self {
Self {
boot: Some(rom),
..Default::default()
})
}
}
pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
self.cartridge = Some(Cartridge::new(path)?);
Ok(())
pub(crate) fn load_cart(&mut self, rom: Vec<u8>) {
self.cart = Some(Cartridge::new(rom));
}
pub(crate) fn rom_title(&self) -> Option<&str> {
self.cartridge.as_ref()?.title()
pub(crate) fn cart_title(&self) -> Option<&str> {
self.cart.as_ref()?.title()
}
#[allow(dead_code)]
@@ -104,13 +97,13 @@ impl Bus {
}
}
match self.cartridge.as_ref() {
match self.cart.as_ref() {
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
}
}
0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM
0xA000..=0xBFFF => match self.cartridge.as_ref() {
0xA000..=0xBFFF => match self.cart.as_ref() {
// 8KB External RAM
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
@@ -157,7 +150,7 @@ impl BusIo for Bus {
}
}
match self.cartridge.as_ref() {
match self.cart.as_ref() {
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
}
@@ -169,7 +162,7 @@ impl BusIo for Bus {
_ => self.ppu.read_byte(addr),
}
}
0xA000..=0xBFFF => match self.cartridge.as_ref() {
0xA000..=0xBFFF => match self.cart.as_ref() {
// 8KB External RAM
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
@@ -262,7 +255,7 @@ impl BusIo for Bus {
0x0000..=0x7FFF => {
// 16KB ROM bank 00 (ends at 0x3FFF)
// and 16KB ROM Bank 01 -> NN (switchable via MB)
match self.cartridge.as_mut() {
match self.cart.as_mut() {
Some(cart) => cart.write_byte(addr, byte),
None => panic!("Tried to write into non-existent cartridge"),
}
@@ -276,7 +269,7 @@ impl BusIo for Bus {
}
0xA000..=0xBFFF => {
// 8KB External RAM
match self.cartridge.as_mut() {
match self.cart.as_mut() {
Some(cart) => cart.write_byte(addr, byte),
None => panic!("Tried to write into non-existent cartridge"),
}

View File

@@ -1,7 +1,3 @@
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use crate::bus::BusIo;
const RAM_SIZE_ADDRESS: usize = 0x0149;
@@ -17,19 +13,15 @@ pub(crate) struct Cartridge {
}
impl Cartridge {
pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> {
let mut memory = vec![];
let mut rom = File::open(path)?;
rom.read_to_end(&mut memory)?;
pub(crate) fn new(memory: Vec<u8>) -> Self {
let title = Self::find_title(&memory);
eprintln!("Cartridge Title: {:?}", title);
Ok(Self {
Self {
mbc: Self::detect_mbc(&memory),
title,
memory,
})
}
}
fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {

View File

@@ -1,10 +1,7 @@
use crate::apu::Apu;
use crate::bus::{Bus, BusIo};
use crate::instruction::cycle::Cycle;
use crate::bus::{Bus, BusIo, BOOT_SIZE};
use crate::instruction::Instruction;
use crate::interrupt::{InterruptEnable, InterruptFlag};
use crate::joypad::Joypad;
use crate::ppu::Ppu;
use crate::Cycle;
use bitfield::bitfield;
use std::fmt::{Display, Formatter, Result as FmtResult};
@@ -18,7 +15,7 @@ pub struct Cpu {
}
impl Cpu {
pub fn new() -> Self {
pub(crate) fn without_boot() -> Self {
Self {
reg: Registers {
a: 0x01,
@@ -36,11 +33,11 @@ impl Cpu {
}
}
pub fn boot_new(path: &str) -> anyhow::Result<Self> {
Ok(Self {
bus: Bus::with_boot(path)?,
pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self {
Self {
bus: Bus::with_boot(rom),
..Default::default()
})
}
}
pub(crate) fn ime(&self) -> ImeState {
@@ -72,14 +69,6 @@ impl Cpu {
_ => None,
}
}
pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
self.bus.load_cartridge(path)
}
pub fn rom_title(&self) -> Option<&str> {
self.bus.rom_title()
}
}
impl Cpu {
@@ -133,7 +122,7 @@ impl Cpu {
self.bus.clock();
let elapsed = match kind {
ImeEnabled | NonePending => Cycle::new(4),
ImeEnabled | NonePending => 4,
SomePending => todo!("Implement HALT bug"),
};
@@ -146,11 +135,11 @@ impl Cpu {
self.handle_ei();
// For use in Blargg's Test ROMs
// if self.read_byte(0xFF02) == 0x81 {
// let c = self.read_byte(0xFF01) as char;
// self.write_byte(0xFF02, 0x00);
// eprint!("{}", c);
// }
if self.read_byte(0xFF02) == 0x81 {
let c = self.read_byte(0xFF01) as char;
self.write_byte(0xFF02, 0x00);
eprint!("{}", c);
}
elapsed
}
@@ -167,16 +156,12 @@ impl BusIo for Cpu {
}
impl Cpu {
pub fn ppu(&mut self) -> &Ppu {
&self.bus.ppu
pub(crate) fn bus(&self) -> &Bus {
&self.bus
}
pub fn apu_mut(&mut self) -> &mut Apu {
&mut self.bus.apu
}
pub(crate) fn joypad_mut(&mut self) -> &mut Joypad {
&mut self.bus.joypad
pub(crate) fn bus_mut(&mut self) -> &mut Bus {
&mut self.bus
}
fn handle_ei(&mut self) {

View File

@@ -1,67 +1,121 @@
use crate::cpu::Cpu as SM83;
use crate::instruction::cycle::Cycle;
use crate::joypad;
use crate::ppu::Ppu;
use anyhow::Result;
use crate::apu::gen::SampleProducer;
use crate::cpu::Cpu;
use crate::joypad::{self, Joypad};
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
use gilrs::Gilrs;
use std::time::Duration;
use winit_input_helper::WinitInputHelper;
pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED);
pub const CYCLES_IN_FRAME: Cycle = Cycle::new(456 * 154); // 456 Cycles times 154 scanlines
pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines
pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> {
let mut cpu = match boot_path {
Some(path) => SM83::boot_new(path)?,
None => SM83::new(),
};
eprintln!("Initialized GB Emulator");
cpu.load_cartridge(rom_path)?;
Ok(cpu)
}
pub fn rom_title(game_boy: &SM83) -> &str {
game_boy.rom_title().unwrap_or(DEFAULT_TITLE)
}
pub fn run(
game_boy: &mut SM83,
gamepad: &mut Gilrs,
input: &WinitInputHelper,
target: Cycle,
) -> Cycle {
let mut elapsed = Cycle::new(0);
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle {
let mut elapsed = 0;
if let Some(event) = gamepad.next_event() {
joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
}
joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
while elapsed < target {
elapsed += game_boy.step();
joypad::handle_gamepad_input(emu.joyp_mut(), event);
}
elapsed
}
pub fn run_frame(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle {
let mut elapsed = Cycle::new(0);
if let Some(event) = gamepad.next_event() {
joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
}
joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
joypad::handle_keyboard_input(emu.joyp_mut(), key);
while elapsed < CYCLES_IN_FRAME {
elapsed += game_boy.step();
elapsed += emu.step();
}
elapsed
}
pub fn draw(ppu: &Ppu, frame: &mut [u8]) {
ppu.copy_to_gui(frame);
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 Emulator {
fn new(cpu: Cpu) -> Self {
Self {
cpu,
timestamp: Default::default(),
}
}
fn step(&mut self) -> Cycle {
self.cpu.step()
}
fn load_cart(&mut self, rom: Vec<u8>) {
self.cpu.bus_mut().load_cart(rom)
}
fn joyp_mut(&mut self) -> &mut Joypad {
&mut self.cpu.bus_mut().joypad
}
pub fn set_prod(&mut self, prod: SampleProducer<f32>) {
self.cpu.bus_mut().apu.attach_producer(prod)
}
pub fn title(&self) -> &str {
self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE)
}
}
pub mod build {
use std::fs::File;
use std::io::{Read, Result};
use std::path::Path;
use crate::bus::BOOT_SIZE;
use crate::cpu::Cpu;
use super::Emulator;
#[derive(Debug, Default)]
pub struct EmulatorBuilder {
boot: Option<[u8; BOOT_SIZE]>,
cart: Option<Vec<u8>>,
}
impl EmulatorBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn with_boot<P: AsRef<Path>>(mut self, path: P) -> Result<Self> {
let mut file = File::open(path.as_ref())?;
let mut buf = [0x00; BOOT_SIZE];
file.read_exact(&mut buf)?;
self.boot = Some(buf);
Ok(self)
}
pub fn with_cart<P: AsRef<Path>>(mut self, path: P) -> Result<Self> {
let mut file = File::open(path.as_ref())?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
self.cart = Some(buf);
Ok(self)
}
pub fn finish(mut self) -> Emulator {
let mut emu = Emulator::new(match self.boot {
Some(rom) => Cpu::with_boot(rom),
None => Cpu::without_boot(),
});
if let Some(rom) = self.cart.take() {
emu.load_cart(rom)
}
emu
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
pub use apu::gen::AudioSPSC;
pub use instruction::cycle::Cycle;
pub type Cycle = u64;
pub const GB_WIDTH: usize = 160;
pub const GB_HEIGHT: usize = 144;

View File

@@ -1,17 +1,20 @@
use std::convert::TryInto;
use anyhow::{anyhow, Result};
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
use gb::emu::build::EmulatorBuilder;
use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH};
use gilrs::Gilrs;
use pixels::{PixelsBuilder, SurfaceTexture};
use rodio::{OutputStream, Sink};
use winit::dpi::LogicalSize;
use winit::dpi::{LogicalSize, PhysicalSize};
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};
use winit_input_helper::WinitInputHelper;
const WINDOW_SCALE: f64 = 2.0;
const AUDIO_ENABLED: bool = true;
const WINDOW_SCALE: usize = 3;
const AUDIO_ENABLED: bool = false;
fn main() -> Result<()> {
let app = App::new(crate_name!())
@@ -38,13 +41,15 @@ fn main() -> Result<()> {
)
.get_matches();
let rom_path = m
.value_of("rom")
.expect("Required value 'rom' was provided");
let mut emu_build =
EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?;
let mut game_boy =
gb::emu::init(m.value_of("boot"), rom_path).expect("Initialize DMG-01 Emulator");
let rom_title = gb::emu::rom_title(&game_boy);
if let Some(path) = m.value_of("boot") {
emu_build = emu_build.with_boot(path)?;
}
let mut emu = emu_build.finish();
let rom_title = emu.title();
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
@@ -68,10 +73,14 @@ fn main() -> Result<()> {
if AUDIO_ENABLED {
let spsc: AudioSPSC<f32> = Default::default();
let (prod, cons) = spsc.init();
let sink = Sink::try_new(&stream_handle)?;
sink.append(cons);
sink.set_volume(0.1); // TODO: Is this the right way to go about this?
game_boy.apu_mut().attach_producer(prod);
let sink = {
let s = Sink::try_new(&stream_handle)?;
s.append(cons);
s.set_volume(0.1);
s
};
emu.set_prod(prod);
std::thread::spawn(move || {
sink.sleep_until_end();
@@ -102,49 +111,34 @@ fn main() -> Result<()> {
pixels.resize_surface(size.width, size.height);
}
cycle_count += gb::emu::run_frame(&mut game_boy, &mut gamepad, &input);
cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input);
if cycle_count >= gb::emu::CYCLES_IN_FRAME {
cycle_count %= gb::emu::CYCLES_IN_FRAME;
gb::emu::draw(game_boy.ppu(), pixels.get_frame());
let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels
.get_frame()
.try_into()
.expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4");
gb::emu::draw_frame(&emu, buf);
window.request_redraw();
}
}
});
}
#[cfg(not(windows))]
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
let size = LogicalSize::new(
(GB_WIDTH as f64) * WINDOW_SCALE,
(GB_HEIGHT as f64) * WINDOW_SCALE,
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_inner_size(size)
.with_min_inner_size(size)
.with_min_inner_size(logical)
.with_inner_size(physical)
.with_resizable(true)
.with_decorations(true)
.with_transparent(false)
.build(event_loop)?)
}
#[cfg(windows)]
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
use winit::platform::windows::WindowBuilderExtWindows;
let size = LogicalSize::new(
(GB_WIDTH as f64) * WINDOW_SCALE,
(GB_HEIGHT as f64) * WINDOW_SCALE,
);
Ok(WindowBuilder::new()
.with_title(title)
.with_inner_size(size)
.with_min_inner_size(size)
.with_resizable(true)
.with_decorations(true)
.with_transparent(false)
.with_drag_and_drop(false)
.build(event_loop)?)
}

View File

@@ -1,5 +1,5 @@
use crate::bus::BusIo;
use crate::instruction::cycle::Cycle;
use crate::Cycle;
use crate::GB_HEIGHT;
use crate::GB_WIDTH;
use dma::DirectMemoryAccess;
@@ -79,7 +79,7 @@ impl Ppu {
match self.stat.mode() {
PpuMode::OamScan => {
if self.cycle >= 80.into() {
if self.cycle >= 80 {
self.stat.set_mode(PpuMode::Drawing);
}
@@ -88,7 +88,7 @@ impl Ppu {
PpuMode::Drawing => {
if self.ctrl.lcd_enabled() {
// Only Draw when the LCD Is Enabled
self.draw(self.cycle.into());
self.draw(self.cycle);
} else {
self.reset();
}
@@ -125,7 +125,7 @@ impl Ppu {
PpuMode::HBlank => {
// This mode will always end at 456 cycles
if self.cycle >= 456.into() {
if self.cycle >= 456 {
self.cycle %= 456;
self.pos.line_y += 1;
@@ -167,7 +167,7 @@ impl Ppu {
}
}
PpuMode::VBlank => {
if self.cycle > 456.into() {
if self.cycle > 456 {
self.cycle %= 456;
self.pos.line_y += 1;
@@ -228,7 +228,7 @@ impl Ppu {
self.scan_state.next();
}
fn draw(&mut self, _cycle: u32) {
fn draw(&mut self, _cycle: Cycle) {
use FetcherState::*;
let mut iter = self.obj_buffer.iter_mut();
@@ -418,8 +418,8 @@ impl Ppu {
self.frame_buf.swap_with_slice(&mut blank);
}
pub fn copy_to_gui(&self, frame: &mut [u8]) {
frame.copy_from_slice(self.frame_buf.as_ref());
pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
&self.frame_buf
}
fn clock_fifo(&mut self) -> Option<GrayShade> {
@@ -470,7 +470,7 @@ impl Default for Ppu {
fn default() -> Self {
Self {
vram: Box::new([0u8; VRAM_SIZE]),
cycle: Cycle::new(0),
cycle: Default::default(),
frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
int: Default::default(),
ctrl: Default::default(),

View File

@@ -1,4 +1,4 @@
use crate::instruction::cycle::Cycle;
use crate::Cycle;
#[derive(Debug, Default)]
pub(crate) struct DirectMemoryAccess {
@@ -56,7 +56,7 @@ impl DirectMemoryAccess {
}
fn reset(&mut self) {
self.cycle = Cycle::new(0);
self.cycle = 0;
self.state = DmaState::Disabled;
self.start.0 = None;
}

79
src/scheduler.rs Normal file
View File

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