mirror of
https://github.com/FranLMSP/rmg-001.git
synced 2024-11-23 18:21:31 +00:00
Compare commits
No commits in common. "6104f5b077ddcedd79bd52fc5e4ea861b80d9511" and "0957a79f15e970188a5ebddd77aca3e703d8d5c0" have entirely different histories.
6104f5b077
...
0957a79f15
116
src/bus.rs
116
src/bus.rs
@ -1,37 +1,58 @@
|
||||
use std::ops::RangeInclusive;
|
||||
use crate::utils::{
|
||||
get_bit,
|
||||
BitIndex,
|
||||
join_bytes
|
||||
};
|
||||
use crate::rom::ROM;
|
||||
use crate::ppu::{
|
||||
PPU,
|
||||
LCD_STATUS_ADDRESS,
|
||||
LCD_CONTROL_ADDRESS,
|
||||
LCD_Y_ADDRESS,
|
||||
DMA_ADDRESS,
|
||||
};
|
||||
use crate::cpu::{Interrupt};
|
||||
use crate::timer::{Timer};
|
||||
use crate::timer::{TIMER_DIVIDER_REGISTER_ADDRESS};
|
||||
use crate::joypad::{Joypad, JOYPAD_ADDRESS};
|
||||
|
||||
pub const BANK_ZERO: RangeInclusive<u16> = 0x0000..=0x3FFF;
|
||||
pub const BANK_SWITCHABLE: RangeInclusive<u16> = 0x4000..=0x7FFF;
|
||||
pub const VIDEO_RAM: RangeInclusive<u16> = 0x8000..=0x9FFF;
|
||||
pub const EXTERNAL_RAM: RangeInclusive<u16> = 0xA000..=0xBFFF;
|
||||
pub const WORK_RAM_1: RangeInclusive<u16> = 0xC000..=0xCFFF;
|
||||
pub const WORK_RAM_2: RangeInclusive<u16> = 0xD000..=0xDFFF;
|
||||
pub const ECHO_RAM: RangeInclusive<u16> = 0xE000..=0xFDFF;
|
||||
pub const SPRITE_ATTRIBUTE_TABLE: RangeInclusive<u16> = 0xFE00..=0xFE9F;
|
||||
pub const NOT_USABLE: RangeInclusive<u16> = 0xFEA0..=0xFEFF;
|
||||
pub const IO_REGISTERS: RangeInclusive<u16> = 0xFF00..=0xFF7F;
|
||||
pub const HIGH_RAM: RangeInclusive<u16> = 0xFF80..=0xFFFE;
|
||||
pub const INTERRUPT_ENABLE_REGISTER: RangeInclusive<u16> = 0xFFFF..=0xFFFF;
|
||||
pub struct AddressRange {
|
||||
begin: u16,
|
||||
end: u16,
|
||||
}
|
||||
|
||||
impl AddressRange {
|
||||
pub fn begin(&self) -> u16 {
|
||||
self.begin
|
||||
}
|
||||
|
||||
pub fn end(&self) -> u16 {
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn in_range(&self, address: u16) -> bool {
|
||||
address >= self.begin && address <= self.end
|
||||
}
|
||||
}
|
||||
|
||||
pub const BANK_ZERO: AddressRange = AddressRange{begin: 0x0000, end: 0x3FFF};
|
||||
pub const BANK_SWITCHABLE: AddressRange = AddressRange{begin: 0x4000, end: 0x7FFF};
|
||||
pub const VIDEO_RAM: AddressRange = AddressRange{begin: 0x8000, end: 0x9FFF};
|
||||
pub const EXTERNAL_RAM: AddressRange = AddressRange{begin: 0xA000, end: 0xBFFF};
|
||||
pub const WORK_RAM_1: AddressRange = AddressRange{begin: 0xC000, end: 0xCFFF};
|
||||
pub const WORK_RAM_2: AddressRange = AddressRange{begin: 0xD000, end: 0xDFFF};
|
||||
pub const ECHO_RAM: AddressRange = AddressRange{begin: 0xE000, end: 0xFDFF};
|
||||
pub const SPRITE_ATTRIBUTE_TABLE: AddressRange = AddressRange{begin: 0xFE00, end: 0xFE9F};
|
||||
pub const NOT_USABLE: AddressRange = AddressRange{begin: 0xFEA0, end: 0xFEFF};
|
||||
pub const IO_REGISTERS: AddressRange = AddressRange{begin: 0xFF00, end: 0xFF7F};
|
||||
pub const HIGH_RAM: AddressRange = AddressRange{begin: 0xFF80, end: 0xFFFE};
|
||||
pub const INTERRUPT_ENABLE_REGISTER: AddressRange = AddressRange{begin: 0xFFFF, end: 0xFFFF};
|
||||
pub const INTERRUPT_ENABLE_ADDRESS: u16 = 0xFFFF;
|
||||
pub const INTERRUPT_FLAG_ADDRESS: u16 = 0xFF0F;
|
||||
|
||||
pub struct Bus {
|
||||
game_rom: ROM,
|
||||
data: [u8; 0x10000],
|
||||
pub ppu: PPU,
|
||||
pub reset_timer: bool,
|
||||
pub joypad: Joypad,
|
||||
pub timer: Timer,
|
||||
}
|
||||
|
||||
impl Bus {
|
||||
@ -75,27 +96,18 @@ impl Bus {
|
||||
Self {
|
||||
data,
|
||||
game_rom,
|
||||
ppu: PPU::new(),
|
||||
reset_timer: false,
|
||||
joypad: Joypad::new(),
|
||||
timer: Timer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&self, address: u16) -> u8 {
|
||||
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
|
||||
if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) || EXTERNAL_RAM.in_range(address) {
|
||||
return self.game_rom.read(address);
|
||||
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
|
||||
return 0b11100000 | self.data[address as usize];
|
||||
} else if VIDEO_RAM.contains(&address) {
|
||||
return self.ppu.read_vram(address);
|
||||
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
|
||||
return self.ppu.read_oam(address);
|
||||
} else if PPU::is_io_register(address) {
|
||||
return self.ppu.get_register(address);
|
||||
} else if address == JOYPAD_ADDRESS {
|
||||
return self.joypad.read(self.data[address as usize]);
|
||||
} else if Timer::is_io_register(address) {
|
||||
return self.timer.get_register(address);
|
||||
}
|
||||
self.data[address as usize]
|
||||
}
|
||||
@ -109,39 +121,49 @@ impl Bus {
|
||||
// print!("{}", data as char);
|
||||
}
|
||||
|
||||
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
|
||||
if BANK_ZERO.in_range(address) || BANK_SWITCHABLE.in_range(address) || EXTERNAL_RAM.in_range(address) {
|
||||
self.game_rom.write(address, data);
|
||||
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) {
|
||||
} else if WORK_RAM_1.in_range(address) || WORK_RAM_2.in_range(address) {
|
||||
self.data[address as usize] = data;
|
||||
// Copy to the ECHO RAM
|
||||
if address <= 0xDDFF {
|
||||
self.data[(ECHO_RAM.min().unwrap() + (address - WORK_RAM_1.min().unwrap())) as usize] = data;
|
||||
self.data[(ECHO_RAM.begin() + (address - WORK_RAM_1.begin())) as usize] = data;
|
||||
}
|
||||
} else if EXTERNAL_RAM.contains(&address) {
|
||||
self.game_rom.write(address, data);
|
||||
} else if ECHO_RAM.contains(&address) {
|
||||
} else if EXTERNAL_RAM.in_range(address) {
|
||||
// self.game_rom.write(address, data);
|
||||
} else if ECHO_RAM.in_range(address) {
|
||||
self.data[address as usize] = data;
|
||||
self.data[(WORK_RAM_1.min().unwrap() + (address - ECHO_RAM.min().unwrap())) as usize] = data; // Copy to the working RAM
|
||||
} else if Timer::is_io_register(address) {
|
||||
self.timer.set_register(address, data);
|
||||
self.data[(WORK_RAM_1.begin() + (address - ECHO_RAM.begin())) as usize] = data; // Copy to the working RAM
|
||||
} else if address == TIMER_DIVIDER_REGISTER_ADDRESS {
|
||||
self.reset_timer = true;
|
||||
} else if address == LCD_CONTROL_ADDRESS {
|
||||
self.data[address as usize] = data;
|
||||
// Check if LCD is being turned on or off
|
||||
if (get_bit(data, BitIndex::I7) && !get_bit(self.data[address as usize], BitIndex::I7)) ||
|
||||
!get_bit(data, BitIndex::I7) {
|
||||
self.data[LCD_Y_ADDRESS as usize] = 0x00;
|
||||
// Set Hblank
|
||||
let byte = self.data[LCD_STATUS_ADDRESS as usize];
|
||||
self.data[LCD_STATUS_ADDRESS as usize] = byte & 0b11111100;
|
||||
}
|
||||
} else if address == LCD_Y_ADDRESS {
|
||||
// println!("Write to LCD_Y not allowed");
|
||||
} else if address == LCD_STATUS_ADDRESS {
|
||||
let byte = self.data[address as usize];
|
||||
self.data[address as usize] = (data & 0b11111000) | (byte & 0b00000111);
|
||||
} else if address == JOYPAD_ADDRESS {
|
||||
let byte = self.data[address as usize];
|
||||
self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111);
|
||||
} else if VIDEO_RAM.contains(&address) {
|
||||
return self.ppu.write_vram(address, data);
|
||||
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
|
||||
return self.ppu.write_oam(address, data);
|
||||
} else if address == DMA_ADDRESS {
|
||||
// the idea is: when something gets written to $FF46, multiply it by 0x100, then copy 160 bytes starting from that memory location into OAM
|
||||
self.data[address as usize] = data;
|
||||
let source = (data as u16) * 0x100;
|
||||
let mut count: u16 = 0;
|
||||
let oam_addr = SPRITE_ATTRIBUTE_TABLE.min().unwrap();
|
||||
let source = (data as usize) * 0x100;
|
||||
let mut count = 0;
|
||||
let oam_addr = SPRITE_ATTRIBUTE_TABLE.begin() as usize;
|
||||
while count < 160 {
|
||||
self.ppu.write_oam(oam_addr + count, self.data[(source + count) as usize]);
|
||||
self.data[oam_addr + count] = self.data[source + count];
|
||||
count += 1;
|
||||
}
|
||||
} else if PPU::is_io_register(address) {
|
||||
self.ppu.set_register(address, data);
|
||||
} else {
|
||||
self.data[address as usize] = data;
|
||||
}
|
||||
|
137
src/cpu.rs
137
src/cpu.rs
@ -921,7 +921,7 @@ impl CPU {
|
||||
if interrupts != 0 {
|
||||
self.is_halted = false;
|
||||
}
|
||||
if !self.ime || interrupts == 0 {
|
||||
if !self.ime {
|
||||
return None;
|
||||
}
|
||||
|
||||
@ -1385,10 +1385,11 @@ impl CPU {
|
||||
let prev_value = self.registers.get(register);
|
||||
self.registers.increment(register, 1);
|
||||
if affect_flags {
|
||||
let byte_compare = match register.is_8bit() {
|
||||
true => prev_value.to_be_bytes()[1],
|
||||
false => prev_value.to_be_bytes()[0],
|
||||
};
|
||||
let mut byte_compare = 0;
|
||||
match register.is_8bit() {
|
||||
true => byte_compare = prev_value.to_be_bytes()[1],
|
||||
false => byte_compare = prev_value.to_be_bytes()[0],
|
||||
}
|
||||
let result = self.registers.get(register);
|
||||
self.registers.set_flag(FlagRegister::Substract, false);
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, add_half_carry(byte_compare, 1));
|
||||
@ -1412,10 +1413,11 @@ impl CPU {
|
||||
let prev_value = self.registers.get(register);
|
||||
self.registers.decrement(register, 1);
|
||||
if affect_flags {
|
||||
let byte_compare = match register.is_8bit() {
|
||||
true => prev_value.to_be_bytes()[1],
|
||||
false => prev_value.to_be_bytes()[0],
|
||||
};
|
||||
let mut byte_compare = 0;
|
||||
match register.is_8bit() {
|
||||
true => byte_compare = prev_value.to_be_bytes()[1],
|
||||
false => byte_compare = prev_value.to_be_bytes()[0],
|
||||
}
|
||||
let result = self.registers.get(register);
|
||||
self.registers.set_flag(FlagRegister::Substract, true);
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, sub_half_carry(byte_compare, 1));
|
||||
@ -1592,51 +1594,46 @@ impl CPU {
|
||||
self.registers.increment(Register::PC, 2);
|
||||
match *opcode {
|
||||
Opcode::RLC(register) => {
|
||||
let (val, result) = match register.is_8bit() {
|
||||
true => {
|
||||
let val = self.registers.get_8bit(register);
|
||||
let result = val.rotate_left(1);
|
||||
self.registers.set(register, result as u16);
|
||||
(val, result)
|
||||
},
|
||||
false => {
|
||||
let addr = self.registers.get(register);
|
||||
let val = bus.read(addr);
|
||||
let result = val.rotate_left(1);
|
||||
bus.write(addr, result);
|
||||
(val, result)
|
||||
}
|
||||
};
|
||||
let mut result = 0;
|
||||
let mut val = 0;
|
||||
if register.is_8bit() {
|
||||
val = self.registers.get_8bit(register);
|
||||
result = val.rotate_left(1);
|
||||
self.registers.set(register, result as u16);
|
||||
} else {
|
||||
let addr = self.registers.get(register);
|
||||
val = bus.read(addr);
|
||||
result = val.rotate_left(1);
|
||||
bus.write(addr, result);
|
||||
}
|
||||
self.registers.set_flag(FlagRegister::Zero, result == 0);
|
||||
self.registers.set_flag(FlagRegister::Substract, false);
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, false);
|
||||
self.registers.set_flag(FlagRegister::Carry, get_bit(val, BitIndex::I7));
|
||||
},
|
||||
Opcode::RRC(register) => {
|
||||
let (val, result) = match register.is_8bit() {
|
||||
true => {
|
||||
let val = self.registers.get_8bit(register);
|
||||
let result = val.rotate_right(1);
|
||||
self.registers.set(register, result as u16);
|
||||
(val, result)
|
||||
},
|
||||
false => {
|
||||
let addr = self.registers.get(register);
|
||||
let val = bus.read(addr);
|
||||
let result = val.rotate_right(1);
|
||||
bus.write(addr, result);
|
||||
(val, result)
|
||||
},
|
||||
};
|
||||
let mut result = 0;
|
||||
let mut val = 0;
|
||||
if register.is_8bit() {
|
||||
val = self.registers.get_8bit(register);
|
||||
result = val.rotate_right(1);
|
||||
self.registers.set(register, result as u16);
|
||||
} else {
|
||||
let addr = self.registers.get(register);
|
||||
val = bus.read(addr);
|
||||
result = val.rotate_right(1);
|
||||
bus.write(addr, result);
|
||||
}
|
||||
self.registers.set_flag(FlagRegister::Zero, result == 0);
|
||||
self.registers.set_flag(FlagRegister::Substract, false);
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, false);
|
||||
self.registers.set_flag(FlagRegister::Carry, get_bit(val, BitIndex::I0));
|
||||
},
|
||||
Opcode::RL(register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register),
|
||||
false => bus.read(self.registers.get(register)),
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register),
|
||||
false => val = bus.read(self.registers.get(register)),
|
||||
};
|
||||
let old_carry = self.registers.get_flag(FlagRegister::Carry);
|
||||
let new_carry = get_bit(val, BitIndex::I7);
|
||||
@ -1651,9 +1648,10 @@ impl CPU {
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, false);
|
||||
},
|
||||
Opcode::RR(register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register),
|
||||
false => bus.read(self.registers.get(register)),
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register),
|
||||
false => val = bus.read(self.registers.get(register)),
|
||||
};
|
||||
let old_carry = self.registers.get_flag(FlagRegister::Carry);
|
||||
let new_carry = get_bit(val, BitIndex::I0);
|
||||
@ -1668,9 +1666,10 @@ impl CPU {
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, false);
|
||||
},
|
||||
Opcode::SLA(register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register) as i8,
|
||||
false => bus.read(self.registers.get(register)) as i8,
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register) as i8,
|
||||
false => val = bus.read(self.registers.get(register)) as i8,
|
||||
};
|
||||
let res = val << 1;
|
||||
match register.is_8bit() {
|
||||
@ -1683,9 +1682,10 @@ impl CPU {
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, false);
|
||||
},
|
||||
Opcode::SRA(register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register) as i8,
|
||||
false => bus.read(self.registers.get(register)) as i8,
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register) as i8,
|
||||
false => val = bus.read(self.registers.get(register)) as i8,
|
||||
};
|
||||
let res = val >> 1;
|
||||
match register.is_8bit() {
|
||||
@ -1698,9 +1698,10 @@ impl CPU {
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, false);
|
||||
},
|
||||
Opcode::SRL(register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register),
|
||||
false => bus.read(self.registers.get(register)),
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register),
|
||||
false => val = bus.read(self.registers.get(register)),
|
||||
};
|
||||
let carry = get_bit(val, BitIndex::I0);
|
||||
let val = val >> 1;
|
||||
@ -1714,9 +1715,10 @@ impl CPU {
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, false);
|
||||
},
|
||||
Opcode::SWAP(register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register),
|
||||
false => bus.read(self.registers.get(register)),
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register),
|
||||
false => val = bus.read(self.registers.get(register)),
|
||||
};
|
||||
let val = (val << 4) | (val >> 4);
|
||||
match register.is_8bit() {
|
||||
@ -1729,9 +1731,10 @@ impl CPU {
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, false);
|
||||
},
|
||||
Opcode::BIT(index, register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register),
|
||||
false => bus.read(self.registers.get(register)),
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register),
|
||||
false => val = bus.read(self.registers.get(register)),
|
||||
};
|
||||
let res = get_bit(val, index);
|
||||
self.registers.set_flag(FlagRegister::Zero, !res);
|
||||
@ -1739,9 +1742,10 @@ impl CPU {
|
||||
self.registers.set_flag(FlagRegister::HalfCarry, true);
|
||||
},
|
||||
Opcode::RES(index, register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register),
|
||||
false => bus.read(self.registers.get(register)),
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register),
|
||||
false => val = bus.read(self.registers.get(register)),
|
||||
};
|
||||
let val = set_bit(val, false, index);
|
||||
match register.is_8bit() {
|
||||
@ -1750,9 +1754,10 @@ impl CPU {
|
||||
};
|
||||
},
|
||||
Opcode::SET(index, register) => {
|
||||
let val = match register.is_8bit() {
|
||||
true => self.registers.get_8bit(register),
|
||||
false => bus.read(self.registers.get(register)),
|
||||
let mut val = 0;
|
||||
match register.is_8bit() {
|
||||
true => val = self.registers.get_8bit(register),
|
||||
false => val = bus.read(self.registers.get(register)),
|
||||
};
|
||||
let val = set_bit(val, true, index);
|
||||
match register.is_8bit() {
|
||||
|
@ -3,19 +3,25 @@ use winit_input_helper::WinitInputHelper;
|
||||
use winit::event::{VirtualKeyCode};
|
||||
|
||||
use crate::cpu::{CPU, Cycles, Interrupt};
|
||||
use crate::ppu::PPU;
|
||||
use crate::bus::Bus;
|
||||
use crate::timer::Timer;
|
||||
use crate::joypad::{Button};
|
||||
|
||||
pub struct Emulator {
|
||||
bus: Bus,
|
||||
cpu: CPU,
|
||||
ppu: PPU,
|
||||
bus: Bus,
|
||||
timer: Timer,
|
||||
}
|
||||
|
||||
impl Emulator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
bus: Bus::new(),
|
||||
cpu: CPU::new(),
|
||||
ppu: PPU::new(),
|
||||
bus: Bus::new(),
|
||||
timer: Timer::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,21 +101,12 @@ impl Emulator {
|
||||
self.cpu.reset_cycles();
|
||||
while self.cpu.get_cycles().to_t().0 <= cpu_cycles.0 {
|
||||
self.cpu.run(&mut self.bus);
|
||||
let cycles = self.cpu.get_last_op_cycles().to_t();
|
||||
self.bus.ppu.do_cycles(cycles, frame_buffer);
|
||||
if self.bus.ppu.get_interrupt(Interrupt::VBlank) {
|
||||
self.bus.set_interrupt_flag(Interrupt::VBlank, true);
|
||||
self.bus.ppu.set_interrupt(Interrupt::VBlank, false);
|
||||
}
|
||||
if self.bus.ppu.get_interrupt(Interrupt::LCDSTAT) {
|
||||
self.bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
|
||||
self.bus.ppu.set_interrupt(Interrupt::LCDSTAT, false);
|
||||
}
|
||||
self.bus.timer.do_cycles(cycles);
|
||||
if self.bus.timer.get_interrupt() {
|
||||
self.bus.set_interrupt_flag(Interrupt::Timer, true);
|
||||
self.bus.timer.set_interrupt(false);
|
||||
if self.bus.reset_timer {
|
||||
self.bus.reset_timer = false;
|
||||
self.timer.reset(&mut self.bus);
|
||||
}
|
||||
self.ppu.do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t(), frame_buffer);
|
||||
self.timer.do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t());
|
||||
|
||||
// 1 CPU cycle = 238.42ns
|
||||
// thread::sleep(time::Duration::from_nanos((self.cpu.get_last_op_cycles().0 * 238).try_into().unwrap()));
|
||||
@ -122,21 +119,12 @@ impl Emulator {
|
||||
let mut frame: [u8; 144 * 160 * 4] = [0; 144 * 160 * 4];
|
||||
while !exit {
|
||||
self.cpu.run(&mut self.bus);
|
||||
let cycles = self.cpu.get_last_op_cycles().to_t();
|
||||
self.bus.ppu.do_cycles(cycles, &mut frame);
|
||||
if self.bus.ppu.get_interrupt(Interrupt::VBlank) {
|
||||
self.bus.set_interrupt_flag(Interrupt::VBlank, true);
|
||||
self.bus.ppu.set_interrupt(Interrupt::VBlank, false);
|
||||
}
|
||||
if self.bus.ppu.get_interrupt(Interrupt::LCDSTAT) {
|
||||
self.bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
|
||||
self.bus.ppu.set_interrupt(Interrupt::LCDSTAT, false);
|
||||
}
|
||||
self.bus.timer.do_cycles(cycles);
|
||||
if self.bus.timer.get_interrupt() {
|
||||
self.bus.set_interrupt_flag(Interrupt::Timer, true);
|
||||
self.bus.timer.set_interrupt(false);
|
||||
if self.bus.reset_timer {
|
||||
self.bus.reset_timer = false;
|
||||
self.timer.reset(&mut self.bus);
|
||||
}
|
||||
self.ppu.do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t(), &mut frame);
|
||||
self.timer.do_cycles(&mut self.bus, self.cpu.get_last_op_cycles().to_t());
|
||||
|
||||
// exit = self.cpu.get_exec_calls_count() >= 1258895; // log 1
|
||||
exit = self.cpu.get_exec_calls_count() >= 161502; // log 2
|
||||
|
334
src/ppu.rs
334
src/ppu.rs
@ -3,7 +3,7 @@ use crate::utils::{
|
||||
get_bit,
|
||||
set_bit,
|
||||
};
|
||||
use crate::bus::{SPRITE_ATTRIBUTE_TABLE};
|
||||
use crate::bus::{Bus, SPRITE_ATTRIBUTE_TABLE};
|
||||
use crate::cpu::{Cycles, Interrupt};
|
||||
|
||||
pub const LCD_WIDTH: u32 = 160;
|
||||
@ -25,7 +25,6 @@ pub const OBJECT_PALETTE_0_ADDRESS: u16 = 0xFF48;
|
||||
pub const OBJECT_PALETTE_1_ADDRESS: u16 = 0xFF49;
|
||||
pub const WINDOW_Y_ADDRESS: u16 = 0xFF4A;
|
||||
pub const WINDOW_X_ADDRESS: u16 = 0xFF4B;
|
||||
|
||||
pub const TILE_MAP_ADDRESS: u16 = 0x9800;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@ -37,42 +36,7 @@ enum Pixel {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct RGBA(u8, u8, u8, u8);
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
struct ColorPalette {
|
||||
white: RGBA,
|
||||
light: RGBA,
|
||||
dark: RGBA,
|
||||
black: RGBA,
|
||||
}
|
||||
|
||||
const BACKGROUND_COLORS: ColorPalette = ColorPalette {
|
||||
white: RGBA(0x83, 0xE6, 0xCD, 0),
|
||||
light: RGBA(0x66, 0xAD, 0xC6, 0),
|
||||
dark: RGBA(0x4F, 0x53, 0xAB, 0),
|
||||
black: RGBA(0x3E, 0x24, 0x69, 0),
|
||||
};
|
||||
|
||||
const WINDOW_COLORS: ColorPalette = ColorPalette {
|
||||
white: RGBA(0x83, 0xE6, 0xCD, 0),
|
||||
light: RGBA(0x66, 0xAD, 0xC6, 0),
|
||||
dark: RGBA(0x4F, 0x53, 0xAB, 0),
|
||||
black: RGBA(0x3E, 0x24, 0x69, 0),
|
||||
};
|
||||
|
||||
const SPRITE_0_COLORS: ColorPalette = ColorPalette {
|
||||
white: RGBA(0x83, 0xE6, 0xCD, 0),
|
||||
light: RGBA(0x66, 0xAD, 0xC6, 0),
|
||||
dark: RGBA(0x4F, 0x53, 0xAB, 0),
|
||||
black: RGBA(0x3E, 0x24, 0x69, 0),
|
||||
};
|
||||
|
||||
const SPRITE_1_COLORS: ColorPalette = ColorPalette {
|
||||
white: RGBA(0x83, 0xE6, 0xCD, 0),
|
||||
light: RGBA(0x66, 0xAD, 0xC6, 0),
|
||||
dark: RGBA(0x4F, 0x53, 0xAB, 0),
|
||||
black: RGBA(0x3E, 0x24, 0x69, 0),
|
||||
};
|
||||
struct ColorPalette(u8, u8, u8, u8);
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum LCDControl {
|
||||
@ -130,7 +94,6 @@ struct Sprite {
|
||||
y: u8,
|
||||
tile_number: u8,
|
||||
palette: u8,
|
||||
palette_zero: bool,
|
||||
x_flip: bool,
|
||||
y_flip: bool,
|
||||
over_bg: bool,
|
||||
@ -143,7 +106,7 @@ impl Sprite {
|
||||
self.x
|
||||
}
|
||||
|
||||
pub fn get_pixel(&mut self, lcd_x: u8, lcd_y: u8, vram: &[u8], last_bg_index: u8) -> Option<(Pixel, bool)> {
|
||||
pub fn get_pixel(&mut self, lcd_x: u8, lcd_y: u8, bus: &Bus, last_bg_index: u8) -> Option<Pixel> {
|
||||
if lcd_x < self.x.saturating_sub(8) || lcd_x >= self.x {
|
||||
return None;
|
||||
}
|
||||
@ -185,9 +148,8 @@ impl Sprite {
|
||||
let tile_line = y.rem_euclid(height) * 2;
|
||||
let addr = 0x8000 + (tile_number as u16 * 16) + tile_line as u16;
|
||||
|
||||
let vram_start = 0x8000;
|
||||
let tile_byte_1 = vram[(addr - vram_start) as usize];
|
||||
let tile_byte_2 = vram[(addr - vram_start + 1) as usize];
|
||||
let tile_byte_1 = bus.read(addr);
|
||||
let tile_byte_2 = bus.read(addr + 1);
|
||||
let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
|
||||
self.bit_pixels = Some(bit_pixels_array);
|
||||
|
||||
@ -199,14 +161,12 @@ impl Sprite {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((PPU::get_pixel(PPU::get_palette(bit_pixel, self.palette)), self.palette_zero))
|
||||
Some(PPU::get_pixel(PPU::get_palette(bit_pixel, self.palette)))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PPU {
|
||||
state: bool,
|
||||
vblank_request: bool,
|
||||
lcdstat_request: bool,
|
||||
cycles: Cycles,
|
||||
sprite_buffer: Vec<Sprite>,
|
||||
window_y_counter: u8,
|
||||
@ -220,17 +180,12 @@ pub struct PPU {
|
||||
scroll_y: u8,
|
||||
window_x: u8,
|
||||
window_y: u8,
|
||||
io_registers: [u8; 12],
|
||||
vram: [u8; 0x2000],
|
||||
oam: [u8; 0xA0],
|
||||
}
|
||||
|
||||
impl PPU {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: false,
|
||||
vblank_request: false,
|
||||
lcdstat_request: false,
|
||||
cycles: Cycles(0),
|
||||
sprite_buffer: Vec::new(),
|
||||
window_y_counter: 0,
|
||||
@ -244,81 +199,9 @@ impl PPU {
|
||||
scroll_y: 0,
|
||||
window_x: 0,
|
||||
window_y: 0,
|
||||
io_registers: [0; 12],
|
||||
vram: [0; 0x2000],
|
||||
oam: [0; 0xA0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_interrupt(&mut self, interrupt: Interrupt, val: bool) {
|
||||
match interrupt {
|
||||
Interrupt::VBlank => self.vblank_request = val,
|
||||
Interrupt::LCDSTAT => self.lcdstat_request = val,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_interrupt(&self, interrupt: Interrupt) -> bool {
|
||||
match interrupt {
|
||||
Interrupt::VBlank => self.vblank_request,
|
||||
Interrupt::LCDSTAT => self.lcdstat_request,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_io_register(address: u16) -> bool {
|
||||
address >= 0xFF40 && address <= 0xFF4B
|
||||
}
|
||||
|
||||
pub fn read_vram(&self, address: u16) -> u8 {
|
||||
self.vram[(address - 0x8000) as usize]
|
||||
}
|
||||
|
||||
pub fn write_vram(&mut self, address: u16, data: u8) {
|
||||
self.vram[(address - 0x8000) as usize] = data;
|
||||
}
|
||||
|
||||
pub fn read_oam(&self, address: u16) -> u8 {
|
||||
self.oam[(address - 0xFE00) as usize]
|
||||
}
|
||||
|
||||
pub fn write_oam(&mut self, address: u16, data: u8) {
|
||||
self.oam[(address - 0xFE00) as usize] = data;
|
||||
}
|
||||
|
||||
pub fn get_register(&self, address: u16) -> u8 {
|
||||
self.io_registers[(address - 0xFF40) as usize]
|
||||
}
|
||||
|
||||
pub fn set_register(&mut self, address: u16, data: u8) {
|
||||
if address == LCD_Y_ADDRESS {
|
||||
return;
|
||||
} else if address == LCD_CONTROL_ADDRESS {
|
||||
let address = address - 0xFF40;
|
||||
self.io_registers[address as usize] = data;
|
||||
// Check if LCD is being turned on or off
|
||||
if (get_bit(data, BitIndex::I7) && !get_bit(self.io_registers[address as usize], BitIndex::I7)) ||
|
||||
!get_bit(data, BitIndex::I7)
|
||||
{
|
||||
self.io_registers[LCD_Y_ADDRESS as usize - 0xFF40] = 0x00;
|
||||
// Set Hblank
|
||||
let byte = self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40];
|
||||
self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40] = byte & 0b11111100;
|
||||
}
|
||||
return;
|
||||
} else if address == LCD_STATUS_ADDRESS {
|
||||
let address = address - 0xFF40;
|
||||
let byte = self.io_registers[address as usize];
|
||||
self.io_registers[address as usize] = (data & 0b11111000) | (byte & 0b00000111);
|
||||
} else {
|
||||
self.io_registers[address as usize - 0xFF40] = data;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn force_set_register(&mut self, address: u16, data: u8) {
|
||||
self.io_registers[address as usize - 0xFF40] = data;
|
||||
}
|
||||
|
||||
pub fn reset_cycles(&mut self) {
|
||||
self.cycles.0 = 0;
|
||||
}
|
||||
@ -327,38 +210,38 @@ impl PPU {
|
||||
self.cycles.0 += cycles.0;
|
||||
}
|
||||
|
||||
pub fn do_cycles(&mut self, cycles: Cycles, frame_buffer: &mut [u8]) {
|
||||
pub fn do_cycles(&mut self, bus: &mut Bus, cycles: Cycles, frame_buffer: &mut [u8]) {
|
||||
self.lcd_control_cache = None;
|
||||
if !self.get_lcd_control(LCDControl::LCDEnable) {
|
||||
if !self.get_lcd_control(bus, LCDControl::LCDEnable) {
|
||||
self.increment_cycles(cycles);
|
||||
return;
|
||||
}
|
||||
self.lcd_y = self.get_register(LCD_Y_ADDRESS);
|
||||
self.lcd_y = bus.read(LCD_Y_ADDRESS);
|
||||
self.scroll_x = bus.read(SCROLL_X_ADDRESS);
|
||||
self.scroll_y = bus.read(SCROLL_Y_ADDRESS);
|
||||
self.window_x = bus.read(WINDOW_X_ADDRESS);
|
||||
self.window_y = bus.read(WINDOW_Y_ADDRESS);
|
||||
|
||||
if self.lcd_y < 144 {
|
||||
if self.cycles.0 <= 80 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM)) {
|
||||
if self.cycles.0 <= 80 && !PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM)) {
|
||||
// Mode 2 OAM scan
|
||||
self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM), true);
|
||||
self.stat_interrupt();
|
||||
self.oam_search();
|
||||
} else if self.cycles.0 > 80 && self.cycles.0 <= 80 + 172 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) {
|
||||
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM), true);
|
||||
self.stat_interrupt(bus);
|
||||
self.oam_search(bus);
|
||||
} else if self.cycles.0 > 80 && self.cycles.0 <= 80 + 172 && !PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) {
|
||||
// Mode 3 drawing pixel line. This could also last 289 cycles
|
||||
self.scroll_x = self.get_register(SCROLL_X_ADDRESS);
|
||||
self.scroll_y = self.get_register(SCROLL_Y_ADDRESS);
|
||||
self.window_x = self.get_register(WINDOW_X_ADDRESS);
|
||||
self.window_y = self.get_register(WINDOW_Y_ADDRESS);
|
||||
self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD), true);
|
||||
self.draw_line(frame_buffer);
|
||||
} else if self.cycles.0 > 80 + 172 && self.cycles.0 <= 80 + 172 + 204 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank)) {
|
||||
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD), true);
|
||||
self.draw_line(bus, frame_buffer);
|
||||
} else if self.cycles.0 > 80 + 172 && self.cycles.0 <= 80 + 172 + 204 && !PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank)) {
|
||||
// Mode 0 Horizontal blank. This could last 87 or 204 cycles depending on the mode 3
|
||||
self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank), true);
|
||||
self.stat_interrupt();
|
||||
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank), true);
|
||||
self.stat_interrupt(bus);
|
||||
}
|
||||
} else if self.lcd_y >= 144 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank)) {
|
||||
} else if self.lcd_y >= 144 && !PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank)) {
|
||||
// Mode 1 Vertical blank
|
||||
self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank), true);
|
||||
self.set_interrupt(Interrupt::VBlank, true);
|
||||
self.stat_interrupt();
|
||||
PPU::set_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank), true);
|
||||
bus.set_interrupt_flag(Interrupt::VBlank, true);
|
||||
self.stat_interrupt(bus);
|
||||
}
|
||||
|
||||
self.increment_cycles(cycles);
|
||||
@ -374,53 +257,63 @@ impl PPU {
|
||||
self.lcd_y = 0;
|
||||
self.window_y_counter = 0;
|
||||
}
|
||||
self.force_set_register(LCD_Y_ADDRESS, self.lcd_y);
|
||||
self.stat_interrupt();
|
||||
bus.force_write(LCD_Y_ADDRESS, self.lcd_y);
|
||||
// self.check_lyc(bus);
|
||||
self.stat_interrupt(bus);
|
||||
}
|
||||
}
|
||||
|
||||
fn stat_interrupt(&mut self) {
|
||||
fn stat_interrupt(&mut self, bus: &mut Bus) {
|
||||
let prev_state = self.state;
|
||||
let lyc_compare = self.lcd_y == self.get_register(LCD_Y_COMPARE_ADDRESS);
|
||||
self.set_lcd_status(LCDStatus::LYCFlag, lyc_compare);
|
||||
let lyc_compare = self.lcd_y == bus.read(LCD_Y_COMPARE_ADDRESS);
|
||||
PPU::set_lcd_status(bus, LCDStatus::LYCFlag, lyc_compare);
|
||||
self.state =
|
||||
(
|
||||
lyc_compare &&
|
||||
self.get_lcd_status(LCDStatus::LYCInterrupt)
|
||||
PPU::get_lcd_status(bus, LCDStatus::LYCInterrupt)
|
||||
) ||
|
||||
(
|
||||
self.get_lcd_status(LCDStatus::Mode2OAMInterrupt) &&
|
||||
self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM))
|
||||
PPU::get_lcd_status(bus, LCDStatus::Mode2OAMInterrupt) &&
|
||||
PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM))
|
||||
) || (
|
||||
self.get_lcd_status(LCDStatus::Mode0HBlankInterrupt) &&
|
||||
self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank))
|
||||
PPU::get_lcd_status(bus, LCDStatus::Mode0HBlankInterrupt) &&
|
||||
PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank))
|
||||
) || (
|
||||
self.get_lcd_status(LCDStatus::Mode1VBlankInterrupt) &&
|
||||
self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank))
|
||||
PPU::get_lcd_status(bus, LCDStatus::Mode1VBlankInterrupt) &&
|
||||
PPU::get_lcd_status(bus, LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank))
|
||||
);
|
||||
if self.state && !prev_state {
|
||||
self.set_interrupt(Interrupt::LCDSTAT, self.state);
|
||||
bus.set_interrupt_flag(Interrupt::LCDSTAT, self.state);
|
||||
}
|
||||
}
|
||||
|
||||
fn oam_search(&mut self) {
|
||||
if !self.get_lcd_control(LCDControl::ObjectEnable) {
|
||||
fn check_lyc(&mut self, bus: &mut Bus) {
|
||||
let lyc_compare = self.lcd_y == bus.read(LCD_Y_COMPARE_ADDRESS);
|
||||
PPU::set_lcd_status(bus, LCDStatus::LYCFlag, lyc_compare);
|
||||
if !self.state && lyc_compare && PPU::get_lcd_status(bus, LCDStatus::LYCInterrupt) {
|
||||
bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
|
||||
self.state = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn oam_search(&mut self, bus: &Bus) {
|
||||
self.sprite_buffer = Vec::new();
|
||||
if !self.get_lcd_control(bus, LCDControl::ObjectEnable) {
|
||||
return;
|
||||
}
|
||||
self.sprite_buffer = Vec::new();
|
||||
let palette_0 = self.get_register(OBJECT_PALETTE_0_ADDRESS);
|
||||
let palette_1 = self.get_register(OBJECT_PALETTE_1_ADDRESS);
|
||||
let long_sprites = self.get_lcd_control(LCDControl::ObjectSize);
|
||||
let mut addr = SPRITE_ATTRIBUTE_TABLE.min().unwrap();
|
||||
while addr <= SPRITE_ATTRIBUTE_TABLE.max().unwrap() {
|
||||
let palette_0 = bus.read(OBJECT_PALETTE_0_ADDRESS);
|
||||
let palette_1 = bus.read(OBJECT_PALETTE_1_ADDRESS);
|
||||
let long_sprites = self.get_lcd_control(bus, LCDControl::ObjectSize);
|
||||
let mut addr = SPRITE_ATTRIBUTE_TABLE.begin();
|
||||
while addr <= SPRITE_ATTRIBUTE_TABLE.end() {
|
||||
// The gameboy only supports 10 sprites per line,
|
||||
// but since we are on an emulator we can avoud that limitation
|
||||
if self.sprite_buffer.len() >= 10 {
|
||||
// todo!("Make a setting for the 10 sprites per scanline");
|
||||
break;
|
||||
}
|
||||
let y = self.read_oam(addr);
|
||||
let x = self.read_oam(addr + 1);
|
||||
let y = bus.read(addr);
|
||||
let x = bus.read(addr + 1);
|
||||
|
||||
if x == 0 {
|
||||
addr += 4;
|
||||
@ -440,19 +333,17 @@ impl PPU {
|
||||
}
|
||||
|
||||
|
||||
let tile_number = self.read_oam(addr + 2);
|
||||
let attributes = self.read_oam(addr + 3);
|
||||
let tile_number = bus.read(addr + 2);
|
||||
let attributes = bus.read(addr + 3);
|
||||
|
||||
let palette_zero = !get_bit(attributes, BitIndex::I4);
|
||||
self.sprite_buffer.push(Sprite {
|
||||
x,
|
||||
y,
|
||||
tile_number,
|
||||
palette_zero,
|
||||
is_long: long_sprites,
|
||||
palette: match palette_zero {
|
||||
true => palette_0,
|
||||
false => palette_1,
|
||||
palette: match get_bit(attributes, BitIndex::I4) {
|
||||
true => palette_1,
|
||||
false => palette_0,
|
||||
},
|
||||
x_flip: get_bit(attributes, BitIndex::I5),
|
||||
y_flip: get_bit(attributes, BitIndex::I6),
|
||||
@ -465,10 +356,10 @@ impl PPU {
|
||||
self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x()));
|
||||
}
|
||||
|
||||
fn find_sprite_pixel(&mut self, lcd_x: u8) -> Option<(Pixel, bool)> {
|
||||
fn find_sprite_pixel(&mut self, lcd_x: u8, bus: &Bus) -> Option<Pixel> {
|
||||
let lcd_y = self.lcd_y;
|
||||
for sprite in &mut self.sprite_buffer {
|
||||
if let Some(pixel) = sprite.get_pixel(lcd_x, lcd_y, &self.vram, self.last_bg_index) {
|
||||
if let Some(pixel) = sprite.get_pixel(lcd_x, lcd_y, bus, self.last_bg_index) {
|
||||
return Some(pixel);
|
||||
}
|
||||
}
|
||||
@ -476,11 +367,11 @@ impl PPU {
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn get_lcd_control(&mut self, control: LCDControl) -> bool {
|
||||
pub fn get_lcd_control(&mut self, bus: &Bus, control: LCDControl) -> bool {
|
||||
let byte = match self.lcd_control_cache {
|
||||
Some(byte) => byte,
|
||||
None => {
|
||||
let byte = self.get_register(LCD_CONTROL_ADDRESS);
|
||||
let byte = bus.read(LCD_CONTROL_ADDRESS);
|
||||
self.lcd_control_cache = Some(byte);
|
||||
byte
|
||||
},
|
||||
@ -488,8 +379,8 @@ impl PPU {
|
||||
control.get(byte)
|
||||
}
|
||||
|
||||
pub fn get_lcd_status(&self, status: LCDStatus) -> bool {
|
||||
let byte = self.get_register(LCD_STATUS_ADDRESS);
|
||||
pub fn get_lcd_status(bus: &Bus, status: LCDStatus) -> bool {
|
||||
let byte = bus.read(LCD_STATUS_ADDRESS);
|
||||
match status {
|
||||
LCDStatus::LYCInterrupt => get_bit(byte, BitIndex::I6),
|
||||
LCDStatus::Mode2OAMInterrupt => get_bit(byte, BitIndex::I5),
|
||||
@ -505,8 +396,8 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_lcd_status(&mut self, status: LCDStatus, val: bool) {
|
||||
let mut byte = self.get_register(LCD_STATUS_ADDRESS);
|
||||
fn set_lcd_status(bus: &mut Bus, status: LCDStatus, val: bool) {
|
||||
let mut byte = bus.read(LCD_STATUS_ADDRESS);
|
||||
byte = match status {
|
||||
LCDStatus::LYCInterrupt => set_bit(byte, val, BitIndex::I6),
|
||||
LCDStatus::Mode2OAMInterrupt => set_bit(byte, val, BitIndex::I5),
|
||||
@ -520,15 +411,15 @@ impl PPU {
|
||||
LCDStatusModeFlag::TransferringToLCD => (byte & 0b11111100) | 3,
|
||||
},
|
||||
};
|
||||
self.force_set_register(LCD_STATUS_ADDRESS, byte);
|
||||
bus.force_write(LCD_STATUS_ADDRESS, byte);
|
||||
}
|
||||
|
||||
fn get_tile_bytes(&self, x: u8, y: u8, tilemap_area: u16, default_method: bool) -> (u8, u8) {
|
||||
fn get_tile_bytes(x: u8, y: u8, tilemap_area: u16, default_method: bool, bus: &Bus) -> (u8, u8) {
|
||||
let index_x = x as u16 / 8;
|
||||
let index_y = (y as u16 / 8) * 32;
|
||||
let index = index_x + index_y;
|
||||
let tile_line = (y).rem_euclid(8) * 2;
|
||||
let tile_number = self.read_vram(tilemap_area + index as u16) as u16;
|
||||
let tile_number = bus.read(tilemap_area + index as u16) as u16;
|
||||
let addr = if default_method {
|
||||
0x8000 + tile_line as u16 + (tile_number * 16)
|
||||
} else {
|
||||
@ -538,21 +429,18 @@ impl PPU {
|
||||
(base + tile_line + (tile_number * 16)) as u16
|
||||
};
|
||||
|
||||
(self.read_vram(addr), self.read_vram(addr + 1))
|
||||
(bus.read(addr), bus.read(addr + 1))
|
||||
}
|
||||
|
||||
fn get_window_pixel(&mut self, lcd_x: u8) -> Option<Pixel> {
|
||||
if !self.get_lcd_control(LCDControl::WindowEnable) {
|
||||
return None;
|
||||
}
|
||||
|
||||
fn get_window_pixel(&mut self, lcd_x: u8, bus: &Bus) -> Option<Pixel> {
|
||||
let lcd_y = self.lcd_y;
|
||||
let window_x = self.window_x;
|
||||
let window_y = self.window_y;
|
||||
|
||||
if
|
||||
lcd_y < window_y ||
|
||||
!self.get_lcd_control(bus, LCDControl::WindowEnable) ||
|
||||
lcd_x < window_x.saturating_sub(7) ||
|
||||
lcd_y < window_y ||
|
||||
window_y >= 144 ||
|
||||
window_x.saturating_sub(7) >= 160
|
||||
{
|
||||
@ -572,12 +460,12 @@ impl PPU {
|
||||
bit_pixels_array[bit_pixel_index]
|
||||
},
|
||||
None => {
|
||||
let default_mode = self.get_lcd_control(LCDControl::TileAddressMode);
|
||||
let tilemap_area = match self.get_lcd_control(LCDControl::WindowTileMapAddress) {
|
||||
let default_mode = self.get_lcd_control(bus, LCDControl::TileAddressMode);
|
||||
let tilemap_area = match self.get_lcd_control(bus, LCDControl::WindowTileMapAddress) {
|
||||
true => 0x9C00,
|
||||
false => 0x9800,
|
||||
};
|
||||
let (tile_byte_1, tile_byte_2) = self.get_tile_bytes(x, y, tilemap_area, default_mode);
|
||||
let (tile_byte_1, tile_byte_2) = PPU::get_tile_bytes(x, y, tilemap_area, default_mode, bus);
|
||||
let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
|
||||
self.current_window_pixels = Some(bit_pixels_array);
|
||||
|
||||
@ -590,8 +478,8 @@ impl PPU {
|
||||
Some(PPU::get_pixel(PPU::get_palette(bit_pixel, self.bg_palette)))
|
||||
}
|
||||
|
||||
fn get_background_pixel(&mut self, lcd_x: u8) -> Option<Pixel> {
|
||||
if !self.get_lcd_control(LCDControl::BackgroundPriority) {
|
||||
fn get_background_pixel(&mut self, lcd_x: u8, bus: &Bus) -> Option<Pixel> {
|
||||
if !self.get_lcd_control(bus, LCDControl::BackgroundPriority) {
|
||||
return None;
|
||||
}
|
||||
let lcd_y = self.lcd_y;
|
||||
@ -608,12 +496,12 @@ impl PPU {
|
||||
bit_pixels_array[bit_pixel_index]
|
||||
},
|
||||
None => {
|
||||
let default_mode = self.get_lcd_control(LCDControl::TileAddressMode);
|
||||
let tilemap_area = match self.get_lcd_control(LCDControl::BackgroundTileMapAddress) {
|
||||
let default_mode = self.get_lcd_control(bus, LCDControl::TileAddressMode);
|
||||
let tilemap_area = match self.get_lcd_control(bus, LCDControl::BackgroundTileMapAddress) {
|
||||
true => 0x9C00,
|
||||
false => 0x9800,
|
||||
};
|
||||
let (tile_byte_1, tile_byte_2) = self.get_tile_bytes(x, y, tilemap_area, default_mode);
|
||||
let (tile_byte_1, tile_byte_2) = PPU::get_tile_bytes(x, y, tilemap_area, default_mode, bus);
|
||||
let bit_pixels_array = PPU::get_byte_pixels(tile_byte_1, tile_byte_2);
|
||||
self.current_background_pixels = Some(bit_pixels_array);
|
||||
|
||||
@ -625,7 +513,7 @@ impl PPU {
|
||||
Some(PPU::get_pixel(PPU::get_palette(bit_pixel, self.bg_palette)))
|
||||
}
|
||||
|
||||
fn draw_line(&mut self, frame_buffer: &mut [u8]) {
|
||||
fn draw_line(&mut self, bus: &Bus, frame_buffer: &mut [u8]) {
|
||||
let lcd_y = self.lcd_y;
|
||||
if lcd_y as u32 >= LCD_HEIGHT {
|
||||
return;
|
||||
@ -633,34 +521,30 @@ impl PPU {
|
||||
self.current_background_pixels = None;
|
||||
self.current_window_pixels = None;
|
||||
|
||||
self.bg_palette = self.get_register(BACKGROUND_PALETTE_ADDRESS);
|
||||
self.bg_palette = bus.read(BACKGROUND_PALETTE_ADDRESS);
|
||||
let mut lcd_x: u8 = 0;
|
||||
let mut window_drawn = false;
|
||||
while (lcd_x as u32) < LCD_WIDTH {
|
||||
let idx = (lcd_x as usize + (lcd_y as usize * LCD_WIDTH as usize)) * 4;
|
||||
|
||||
if let Some(window_pixel) = self.get_window_pixel(lcd_x) {
|
||||
window_drawn = true;
|
||||
let rgba = PPU::get_rgba(window_pixel, WINDOW_COLORS);
|
||||
frame_buffer[idx] = rgba[0];
|
||||
frame_buffer[idx + 1] = rgba[1];
|
||||
frame_buffer[idx + 2] = rgba[2];
|
||||
} else if let Some(background_pixel) = self.get_background_pixel(lcd_x) {
|
||||
let rgba = PPU::get_rgba(background_pixel, BACKGROUND_COLORS);
|
||||
if let Some(background_pixel) = self.get_background_pixel(lcd_x, bus) {
|
||||
let rgba = PPU::get_rgba(background_pixel);
|
||||
frame_buffer[idx] = rgba[0];
|
||||
frame_buffer[idx + 1] = rgba[1];
|
||||
frame_buffer[idx + 2] = rgba[2];
|
||||
}
|
||||
if self.get_lcd_control(LCDControl::ObjectEnable) {
|
||||
if let Some((sprite_pixel, palette_zero)) = self.find_sprite_pixel(lcd_x) {
|
||||
let rgba = PPU::get_rgba(sprite_pixel, match palette_zero {
|
||||
true => SPRITE_0_COLORS,
|
||||
false => SPRITE_1_COLORS,
|
||||
});
|
||||
frame_buffer[idx] = rgba[0];
|
||||
frame_buffer[idx + 1] = rgba[1];
|
||||
frame_buffer[idx + 2] = rgba[2];
|
||||
}
|
||||
if let Some(window_pixel) = self.get_window_pixel(lcd_x, bus) {
|
||||
window_drawn = true;
|
||||
let rgba = PPU::get_rgba(window_pixel);
|
||||
frame_buffer[idx] = rgba[0];
|
||||
frame_buffer[idx + 1] = rgba[1];
|
||||
frame_buffer[idx + 2] = rgba[2];
|
||||
}
|
||||
if let Some(sprite_pixel) = self.find_sprite_pixel(lcd_x, bus) {
|
||||
let rgba = PPU::get_rgba(sprite_pixel);
|
||||
frame_buffer[idx] = rgba[0];
|
||||
frame_buffer[idx + 1] = rgba[1];
|
||||
frame_buffer[idx + 2] = rgba[2];
|
||||
}
|
||||
|
||||
lcd_x += 1;
|
||||
@ -690,12 +574,12 @@ impl PPU {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rgba(pixel: Pixel, colors: ColorPalette) -> [u8; 4] {
|
||||
fn get_rgba(pixel: Pixel) -> [u8; 4] {
|
||||
match pixel {
|
||||
Pixel::White => [colors.white.0, colors.white.1, colors.white.2, colors.white.3],
|
||||
Pixel::Light => [colors.light.0, colors.light.1, colors.light.2, colors.light.3],
|
||||
Pixel::Dark => [ colors.dark.0, colors.dark.1, colors.dark.2, colors.dark.3],
|
||||
Pixel::Black => [colors.black.0, colors.black.1, colors.black.2, colors.black.3],
|
||||
Pixel::White => [255, 255, 255, 0],
|
||||
Pixel::Light => [192, 192, 192, 0],
|
||||
Pixel::Dark => [81, 81, 81, 0],
|
||||
Pixel::Black => [0, 0, 0, 0],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ use crate::frames::Frames;
|
||||
use crate::cpu::{Cycles};
|
||||
use crate::ppu::{WIDTH, HEIGHT};
|
||||
|
||||
use std::{thread, time};
|
||||
|
||||
use log::error;
|
||||
use pixels::{wgpu, Pixels, PixelsBuilder, SurfaceTexture};
|
||||
use winit::dpi::LogicalSize;
|
||||
|
17
src/rom.rs
17
src/rom.rs
@ -26,12 +26,14 @@ enum MBC {
|
||||
MBC1,
|
||||
MBC2,
|
||||
MBC3,
|
||||
MBC4,
|
||||
MBC5,
|
||||
MBC6,
|
||||
MBC7,
|
||||
HuC1,
|
||||
HuC3,
|
||||
MMM01,
|
||||
MBC1M,
|
||||
PocketCamera,
|
||||
BandaiTIMA5,
|
||||
}
|
||||
@ -188,15 +190,15 @@ impl ROM {
|
||||
};
|
||||
},
|
||||
MBC::MBC1 => {
|
||||
if BANK_ZERO.contains(&address) {
|
||||
if BANK_ZERO.in_range(address) {
|
||||
return self.data[address as usize];
|
||||
} else if BANK_SWITCHABLE.contains(&address) {
|
||||
} else if BANK_SWITCHABLE.in_range(address) {
|
||||
return self.data[((self.rom_bank as usize * 0x4000) + (address as usize & 0x3FFF)) as usize];
|
||||
} else if EXTERNAL_RAM.contains(&address) {
|
||||
} else if EXTERNAL_RAM.in_range(address) {
|
||||
if !self.info.has_ram {
|
||||
return 0xFF;
|
||||
}
|
||||
return match self.ram.get((address - EXTERNAL_RAM.min().unwrap() + (0x2000 * self.ram_bank as u16)) as usize) {
|
||||
return match self.ram.get((address - EXTERNAL_RAM.begin() + (0x2000 * self.ram_bank as u16)) as usize) {
|
||||
Some(data) => *data,
|
||||
None => 0xFF,
|
||||
};
|
||||
@ -205,13 +207,14 @@ impl ROM {
|
||||
},
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
self.data[address as usize]
|
||||
}
|
||||
|
||||
pub fn write(&mut self, address: u16, data: u8) {
|
||||
match self.info.mbc {
|
||||
MBC::NoMBC => {},
|
||||
MBC::MBC1 => {
|
||||
if address <= 0x1FFF { // RAM enable register
|
||||
if address >= 0x0000 && address <= 0x1FFF { // RAM enable register
|
||||
if !self.info.has_ram {
|
||||
return;
|
||||
}
|
||||
@ -232,11 +235,11 @@ impl ROM {
|
||||
1 => BankingMode::Advanced,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else if EXTERNAL_RAM.contains(&address) {
|
||||
} else if EXTERNAL_RAM.in_range(address) {
|
||||
if !self.ram_enable || !self.info.has_ram {
|
||||
return;
|
||||
}
|
||||
let address = address as usize - EXTERNAL_RAM.min().unwrap() as usize + (EXTERNAL_RAM.min().unwrap() as usize * self.ram_bank as usize);
|
||||
let address = address as usize - EXTERNAL_RAM.begin() as usize + (EXTERNAL_RAM.begin() as usize * self.ram_bank as usize);
|
||||
if let Some(elem) = self.ram.get_mut(address) {
|
||||
*elem = data;
|
||||
}
|
||||
|
70
src/timer.rs
70
src/timer.rs
@ -1,4 +1,5 @@
|
||||
use crate::cpu::{Cycles};
|
||||
use crate::cpu::{Interrupt, Cycles};
|
||||
use crate::bus::Bus;
|
||||
use crate::utils::{
|
||||
BitIndex,
|
||||
get_bit,
|
||||
@ -13,9 +14,7 @@ pub struct Timer {
|
||||
divider: u16,
|
||||
prev_result: bool,
|
||||
is_enabled: bool,
|
||||
interrupt: bool,
|
||||
control: u8,
|
||||
io_registers: [u8; 4],
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
@ -25,82 +24,49 @@ impl Timer {
|
||||
divider: 0,
|
||||
control: 0,
|
||||
prev_result: false,
|
||||
interrupt: false,
|
||||
is_enabled: false,
|
||||
io_registers: [0; 4],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_io_register(address: u16) -> bool {
|
||||
address >= 0xFF04 && address <= 0xFF07
|
||||
}
|
||||
|
||||
pub fn get_register(&self, address: u16) -> u8 {
|
||||
if address == TIMER_DIVIDER_REGISTER_ADDRESS {
|
||||
return self.read_divider();
|
||||
}
|
||||
self.io_registers[(address - 0xFF04) as usize]
|
||||
}
|
||||
|
||||
pub fn set_register(&mut self, address: u16, data: u8) {
|
||||
if address == TIMER_DIVIDER_REGISTER_ADDRESS {
|
||||
self.divider = 0;
|
||||
self.io_registers[(TIMER_DIVIDER_REGISTER_ADDRESS - 0xFF04) as usize] = 0;
|
||||
} else {
|
||||
self.io_registers[(address - 0xFF04) as usize] = data;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_interrupt(&self) -> bool {
|
||||
self.interrupt
|
||||
}
|
||||
|
||||
pub fn set_interrupt(&mut self, val: bool) {
|
||||
self.interrupt = val
|
||||
}
|
||||
|
||||
pub fn read_divider(&self) -> u8 {
|
||||
self.divider.to_be_bytes()[0]
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
println!("Reset divider");
|
||||
pub fn reset(&mut self, bus: &mut Bus) {
|
||||
self.divider = 0;
|
||||
bus.force_write(TIMER_DIVIDER_REGISTER_ADDRESS, 0);
|
||||
}
|
||||
|
||||
pub fn do_cycles(&mut self, cycles: Cycles) {
|
||||
self.is_enabled = self.is_timer_enabled();
|
||||
self.control = self.get_register(TIMER_CONTROL_ADDRESS);
|
||||
pub fn do_cycles(&mut self, bus: &mut Bus, cycles: Cycles) {
|
||||
self.is_enabled = Timer::is_timer_enabled(bus);
|
||||
self.control = bus.read(TIMER_CONTROL_ADDRESS);
|
||||
let mut count = 0;
|
||||
while count < cycles.0 {
|
||||
self.cycle();
|
||||
self.cycle(bus);
|
||||
count += 1;
|
||||
}
|
||||
bus.force_write(TIMER_DIVIDER_REGISTER_ADDRESS, self.divider.to_be_bytes()[0]);
|
||||
}
|
||||
|
||||
fn cycle(&mut self) {
|
||||
fn cycle(&mut self, bus: &mut Bus) {
|
||||
self.divider = self.divider.wrapping_add(1);
|
||||
|
||||
let result = self.is_enabled && self.get_tima_rate();
|
||||
let result = self.is_enabled && self.get_tima_rate(bus);
|
||||
|
||||
if self.prev_result && !result {
|
||||
let tima = self.get_register(TIMER_COUNTER_ADDRESS).wrapping_add(1);
|
||||
let tima = bus.read(TIMER_COUNTER_ADDRESS).wrapping_add(1);
|
||||
if tima == 0 {
|
||||
self.set_register(TIMER_COUNTER_ADDRESS, self.get_register(TIMER_MODULO_ADDRESS));
|
||||
self.interrupt = true;
|
||||
bus.write(TIMER_COUNTER_ADDRESS, bus.read(TIMER_MODULO_ADDRESS));
|
||||
bus.set_interrupt_flag(Interrupt::Timer, true);
|
||||
} else {
|
||||
self.set_register(TIMER_COUNTER_ADDRESS, tima);
|
||||
bus.write(TIMER_COUNTER_ADDRESS, tima);
|
||||
}
|
||||
}
|
||||
|
||||
self.prev_result = result;
|
||||
}
|
||||
|
||||
fn is_timer_enabled(&self) -> bool {
|
||||
get_bit(self.get_register(TIMER_CONTROL_ADDRESS), BitIndex::I2)
|
||||
fn is_timer_enabled(bus: &Bus) -> bool {
|
||||
get_bit(bus.read(TIMER_CONTROL_ADDRESS), BitIndex::I2)
|
||||
}
|
||||
|
||||
fn get_tima_rate(&self) -> bool {
|
||||
fn get_tima_rate(&self, bus: &Bus) -> bool {
|
||||
let clock_select = self.control & 0b0000_0011;
|
||||
match clock_select {
|
||||
0b00 => ((self.divider >> 9) & 1) == 1,
|
||||
|
Loading…
Reference in New Issue
Block a user