Skip to content

Commit

Permalink
fix: OBJ white/transparency confusion, implement interrupt dispatch T…
Browse files Browse the repository at this point in the history
…OCTTOU found in hardware
  • Loading branch information
EliseZeroTwo committed Jan 26, 2024
1 parent 8e47afa commit d60de54
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 80 deletions.
6 changes: 3 additions & 3 deletions meowgb-core/src/gameboy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,9 @@ impl<S: SerialWriter> Gameboy<S> {
0xFF44 => {} // LY is read only
0xFF45 => self.ppu.set_lyc(&mut self.interrupts, value),
0xFF46 => self.dma.init_request(value),
0xFF47 => self.ppu.bgp.write_bgp(value),
0xFF48 => self.ppu.obp[0].write_obp(value),
0xFF49 => self.ppu.obp[1].write_obp(value),
0xFF47 => self.ppu.bgp.write(value),
0xFF48 => self.ppu.obp[0].write(value),
0xFF49 => self.ppu.obp[1].write(value),
0xFF4A => self.ppu.registers.wy = value,
0xFF4B => self.ppu.registers.wx = value,
0xFF4C..=0xFF4E => {} // Unused
Expand Down
61 changes: 37 additions & 24 deletions meowgb-core/src/gameboy/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub struct Registers {
pub current_prefixed_opcode: Option<u8>,
pub mem_read_hold: Option<u8>,
pub mem_op_happened: bool,
pub in_interrupt_vector: Option<u8>,
pub in_interrupt_vector: bool,
}

impl PartialEq for Registers {
Expand Down Expand Up @@ -162,26 +162,14 @@ pub fn tick_cpu(state: &mut Gameboy<impl SerialWriter>) {
}

if state.registers.cycle == 0 && state.interrupts.ime {
if state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank() {
state.registers.in_interrupt_vector = Some(0);
state.interrupts.ime = false;
state.interrupts.write_if_vblank(false);
} else if state.interrupts.read_ie_lcd_stat() && state.interrupts.read_if_lcd_stat() {
state.registers.in_interrupt_vector = Some(1);
state.interrupts.ime = false;
state.interrupts.write_if_lcd_stat(false);
} else if state.interrupts.read_ie_timer() && state.interrupts.read_if_timer() {
state.registers.in_interrupt_vector = Some(2);
state.interrupts.ime = false;
state.interrupts.write_if_timer(false);
} else if state.interrupts.read_ie_serial() && state.interrupts.read_if_serial() {
state.registers.in_interrupt_vector = Some(3);
state.interrupts.ime = false;
state.interrupts.write_if_serial(false);
} else if state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad() {
state.registers.in_interrupt_vector = Some(4);
state.registers.in_interrupt_vector = (state.interrupts.read_ie_vblank()
&& state.interrupts.read_if_vblank())
|| (state.interrupts.read_ie_lcd_stat() && state.interrupts.read_if_lcd_stat())
|| (state.interrupts.read_ie_timer() && state.interrupts.read_if_timer())
|| (state.interrupts.read_ie_serial() && state.interrupts.read_if_serial())
|| (state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad());
if state.registers.in_interrupt_vector {
state.interrupts.ime = false;
state.interrupts.write_if_joypad(false);
}
}

Expand All @@ -196,7 +184,7 @@ pub fn tick_cpu(state: &mut Gameboy<impl SerialWriter>) {
state.registers.pc = state.registers.pc.overflowing_sub(1).0;
}

let result = if let Some(idx) = state.registers.in_interrupt_vector {
let result = if state.registers.in_interrupt_vector {
match state.registers.cycle {
0 => {
// Invalidate prefetch if present
Expand All @@ -214,22 +202,47 @@ pub fn tick_cpu(state: &mut Gameboy<impl SerialWriter>) {
}
4 => {
let original_pc = state.registers.pc;
let idx;

if state.interrupts.read_ie_vblank() && state.interrupts.read_if_vblank() {
idx = 0;
state.interrupts.write_if_vblank(false);
} else if state.interrupts.read_ie_lcd_stat() && state.interrupts.read_if_lcd_stat()
{
idx = 1;
state.interrupts.write_if_lcd_stat(false);
} else if state.interrupts.read_ie_timer() && state.interrupts.read_if_timer() {
idx = 2;
state.interrupts.write_if_timer(false);
} else if state.interrupts.read_ie_serial() && state.interrupts.read_if_serial() {
idx = 3;
state.interrupts.write_if_serial(false);
} else if state.interrupts.read_ie_joypad() && state.interrupts.read_if_joypad() {
idx = 4;
state.interrupts.write_if_joypad(false);
} else {
idx = 5;
println!("IRQ disabled!");
}

// assert_eq!(vector, idx);

state.registers.pc = match idx {
0 => 0x40,
1 => 0x48,
2 => 0x50,
3 => 0x58,
4 => 0x60,
5 => 0x00,
_ => unreachable!(),
};
state.registers.in_interrupt_vector = None;
state.registers.opcode_bytecount = Some(0);
state.registers.in_interrupt_vector = false;
log::debug!(
"Triggering interrupt to {:#X} from {:#X}",
state.registers.pc,
original_pc
);
CycleResult::Finished
CycleResult::FinishedKeepPc
}
_ => unreachable!(),
}
Expand Down
74 changes: 21 additions & 53 deletions meowgb-core/src/gameboy/ppu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,14 @@ impl Palette {
}

pub fn new_obp() -> Self {
Self { id0: Color::Transparent, id1: Color::LGray, id2: Color::DGray, id3: Color::Black }
Self { id0: Color::White, id1: Color::LGray, id2: Color::DGray, id3: Color::Black }
}

pub fn write_bgp(&mut self, value: u8) {
self.id0 = Color::from_bg_2bit(value);
self.id1 = Color::from_bg_2bit(value >> 2);
self.id2 = Color::from_bg_2bit(value >> 4);
self.id3 = Color::from_bg_2bit(value >> 6);
}

pub fn write_obp(&mut self, value: u8) {
self.id0 = Color::from_obj_2bit(value);
self.id1 = Color::from_obj_2bit(value >> 2);
self.id2 = Color::from_obj_2bit(value >> 4);
self.id3 = Color::from_obj_2bit(value >> 6);
pub fn write(&mut self, value: u8) {
self.id0 = Color::from_2bit(value);
self.id1 = Color::from_2bit(value >> 2);
self.id2 = Color::from_2bit(value >> 4);
self.id3 = Color::from_2bit(value >> 6);
}

pub fn value(&self) -> u8 {
Expand Down Expand Up @@ -111,22 +104,19 @@ pub enum Color {
LGray,
DGray,
Black,
Transparent,
}

impl Color {
const WHITE: [u8; PIXEL_SIZE] = [0xe0, 0xf8, 0xd0, 0xFF];
const LGRAY: [u8; PIXEL_SIZE] = [0x88, 0xc0, 0x70, 0xFF];
const DGRAY: [u8; PIXEL_SIZE] = [0x34, 0x68, 0x56, 0xFF];
const BLACK: [u8; PIXEL_SIZE] = [0x08, 0x18, 0x20, 0xFF];
const TRANSPARENT: [u8; PIXEL_SIZE] = [0x00, 0x00, 0x00, 0x00];
pub fn rgba(self) -> &'static [u8; PIXEL_SIZE] {
match self {
Color::White => &Self::WHITE,
Color::LGray => &Self::LGRAY,
Color::DGray => &Self::DGRAY,
Color::Black => &Self::BLACK,
Color::Transparent => &Self::TRANSPARENT,
}
}

Expand All @@ -137,12 +127,11 @@ impl Color {
Self::LGRAY => Some(Self::LGray),
Self::DGRAY => Some(Self::DGray),
Self::BLACK => Some(Self::Black),
Self::TRANSPARENT => Some(Self::Transparent),
_ => None,
}
}

pub fn from_bg_2bit(value: u8) -> Self {
pub fn from_2bit(value: u8) -> Self {
match value & 0b11 {
0 => Self::White,
1 => Self::LGray,
Expand All @@ -152,23 +141,12 @@ impl Color {
}
}

pub fn from_obj_2bit(value: u8) -> Self {
match value & 0b11 {
0 => Self::Transparent,
1 => Self::LGray,
2 => Self::DGray,
3 => Self::Black,
_ => unreachable!(),
}
}

pub fn to_2bit(&self) -> u8 {
match self {
Color::White => 0,
Color::LGray => 1,
Color::DGray => 2,
Color::Black => 3,
Color::Transparent => 0,
}
}

Expand Down Expand Up @@ -226,8 +204,8 @@ impl OAMEntry {
(self.flags >> 5) & 0b1 == 1
}

pub fn palette_number(&self) -> bool {
(self.flags >> 4) & 0b1 == 1
pub fn palette_number(&self) -> usize {
(self.flags >> 4) as usize & 0b1
}
}

Expand Down Expand Up @@ -540,17 +518,11 @@ impl Ppu {
(self.registers.lcdc >> 7) == 1
}

pub fn set_mode_next(&mut self, mode: PPUMode) {
pub fn set_mode(&mut self, mode: PPUMode) {
self.last_mode = Some(self.mode());
self.registers.mode = mode;
}

pub fn set_mode_now(&mut self, interrupts: &mut Interrupts, mode: PPUMode) {
let last_mode = self.mode();
self.registers.mode = mode;
self.update_mode(interrupts, last_mode)
}

fn update_mode(&mut self, interrupts: &mut Interrupts, last_mode: PPUMode) {
let mode = self.mode();
self.registers.cycles_since_last_last_mode_start_increment[mode.mode_flag() as usize] = 0;
Expand Down Expand Up @@ -657,7 +629,7 @@ impl Ppu {
self.total_dots += 1;

if self.current_dot == 80 {
self.set_mode_next(PPUMode::TransferringData);
self.set_mode(PPUMode::TransferringData);
assert_eq!(self.total_dots, 80);
} else {
assert!(self.current_dot < 80);
Expand Down Expand Up @@ -696,12 +668,8 @@ impl Ppu {
self.dot_target,
self.current_draw_state
);
// assert_eq!(self.total_dots, match self.first_frame && self.first_line {
// true => self.dot_target,
// false => 80 + self.dot_target
// });
assert_eq!(self.current_draw_state, Some(LineDrawingState::Finished));
self.set_mode_next(PPUMode::HBlank);
self.set_mode(PPUMode::HBlank);
}

false
Expand All @@ -714,7 +682,7 @@ impl Ppu {
assert_ne!(self.dot_target, 0);
}
if self.first_line && self.current_dot == 76 && self.dot_target == 0 {
self.set_mode_next(PPUMode::TransferringData);
self.set_mode(PPUMode::TransferringData);
} else if self.dot_target != 0 && self.current_dot == self.dot_target {
self.set_scanline(interrupts, self.registers.ly + 1);

Expand All @@ -734,7 +702,7 @@ impl Ppu {
false => PPUMode::SearchingOAM,
};

self.set_mode_next(next_mode);
self.set_mode(next_mode);
}

false
Expand All @@ -746,7 +714,7 @@ impl Ppu {
if self.current_dot % 456 == 0 {
if self.registers.ly >= 153 {
self.set_scanline(interrupts, 0);
self.set_mode_next(PPUMode::SearchingOAM);
self.set_mode(PPUMode::SearchingOAM);
self.first_frame = false;
true
} else {
Expand Down Expand Up @@ -932,14 +900,14 @@ impl Ppu {
assert!(sprite_y_idx < 8);
}

let color_id =
self.read_obj_tile_colour_id(tile_idx, sprite_x_idx, sprite_y_idx);
let palette = &self.obp[sprite.palette_number() as usize];
let sprite_color = palette.color_from_2bit(color_id);

let palette_color_idx =
self.read_obj_tile_colour_id(tile_idx, sprite_x_idx, sprite_y_idx); // If the index is 0, it is just treated as being transparent
let sprite_covered = sprite.covered_by_bg_window() && bg_color_id != 0;

if color_id != 0 && !sprite_covered {
if palette_color_idx != 0 && !sprite_covered {
let palette = &self.obp[sprite.palette_number()];
let sprite_color = palette.color_from_2bit(palette_color_idx);

let [r, g, b, a] = *sprite_color.rgba();

self.sprite_framebuffer[framebuffer_offset + 0] = r;
Expand Down

0 comments on commit d60de54

Please sign in to comment.