use crate::gb::ppu::background::BgPixelCgb;
pub fn fetch_window_pixel(
x: u32,
scanline: u8,
vram: &[u8; 0x2000],
lcdc: u8,
wx: u8,
wy: u8,
window_line: u8,
) -> Option<u8> {
if lcdc & 0x20 == 0 {
return None;
}
if scanline < wy {
return None;
}
let win_x_start = wx.saturating_sub(7);
if x < win_x_start as u32 {
return None;
}
let win_x = (x as u8).wrapping_sub(win_x_start);
let win_y = window_line;
let map_base: usize = if lcdc & 0x40 != 0 { 0x1C00 } else { 0x1800 };
let tile_col = (win_x / 8) as usize;
let tile_row = (win_y / 8) as usize;
let tile_index_raw = vram[map_base + tile_row * 32 + tile_col];
let tile_data_start: usize = if lcdc & 0x10 != 0 {
(tile_index_raw as usize) * 16
} else {
(0x1000i32 + (tile_index_raw as i8 as i32) * 16) as usize
};
let row_in_tile = (win_y % 8) as usize;
let bit = 7 - (win_x % 8);
let addr = tile_data_start + row_in_tile * 2;
let low = vram[addr];
let high = vram[addr + 1];
Some(((high >> bit) & 1) << 1 | ((low >> bit) & 1))
}
#[allow(clippy::too_many_arguments)]
pub fn fetch_window_pixel_cgb(
x: u32,
scanline: u8,
vram: &[u8; 0x2000],
vram_bank1: &[u8; 0x2000],
lcdc: u8,
wx: u8,
wy: u8,
window_line: u8,
) -> Option<BgPixelCgb> {
if lcdc & 0x20 == 0 {
return None;
}
if scanline < wy {
return None;
}
let win_x_start = i16::from(wx) - 7;
let visible_win_x_start = win_x_start.max(0) as u32;
if x < visible_win_x_start {
return None;
}
let win_x = ((x as i16) - win_x_start) as u8;
let win_y = window_line;
let map_base: usize = if lcdc & 0x40 != 0 { 0x1C00 } else { 0x1800 };
let tile_col = (win_x / 8) as usize;
let tile_row = (win_y / 8) as usize;
let map_offset = map_base + tile_row * 32 + tile_col;
let tile_index_raw = vram[map_offset];
let attrs = vram_bank1[map_offset];
let palette_num = attrs & 0x07;
let tile_vram_bank = (attrs >> 3) & 0x01;
let x_flip = attrs & 0x20 != 0;
let y_flip = attrs & 0x40 != 0;
let bg_priority = attrs & 0x80 != 0;
let tile_data_start: usize = if lcdc & 0x10 != 0 {
(tile_index_raw as usize) * 16
} else {
(0x1000i32 + (tile_index_raw as i8 as i32) * 16) as usize
};
let mut row_in_tile = (win_y % 8) as usize;
if y_flip {
row_in_tile = 7 - row_in_tile;
}
let pixel_in_tile = win_x % 8;
let bit = if x_flip {
pixel_in_tile
} else {
7 - pixel_in_tile
};
let tile_vram = if tile_vram_bank != 0 {
vram_bank1
} else {
vram
};
let addr = tile_data_start + row_in_tile * 2;
let low = tile_vram[addr];
let high = tile_vram[addr + 1];
let colour_index = ((high >> bit) & 1) << 1 | ((low >> bit) & 1);
Some(BgPixelCgb {
colour_index,
palette_num,
vram_bank: tile_vram_bank,
bg_priority,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn blank_vram() -> [u8; 0x2000] {
[0u8; 0x2000]
}
#[test]
fn test_window_returns_none_when_disabled_in_lcdc() {
let vram = blank_vram();
let lcdc = 0x81u8; let result = fetch_window_pixel(0, 0, &vram, lcdc, 7, 0, 0);
assert_eq!(result, None);
}
#[test]
fn test_window_returns_none_above_wy() {
let vram = blank_vram();
let lcdc = 0xA1u8; let result = fetch_window_pixel(0, 9, &vram, lcdc, 7, 10, 0);
assert_eq!(result, None);
}
#[test]
fn test_window_returns_none_left_of_wx() {
let vram = blank_vram();
let lcdc = 0xA1u8;
let result = fetch_window_pixel(6, 0, &vram, lcdc, 14, 0, 0);
assert_eq!(result, None);
}
#[test]
fn test_window_returns_colour_index_for_pixel_inside_window() {
let vram = blank_vram();
let lcdc = 0xA1u8; let result = fetch_window_pixel(0, 0, &vram, lcdc, 7, 0, 0);
assert_eq!(result, Some(0));
}
#[test]
fn test_window_tile_data_colour_bits_from_vram() {
let mut vram = blank_vram();
vram[0x0010] = 0xFF;
vram[0x0011] = 0xFF;
vram[0x1800] = 0x01;
let lcdc = 0xB1u8;
let result = fetch_window_pixel(0, 0, &vram, lcdc, 7, 0, 0);
assert_eq!(result, Some(3));
}
#[test]
fn test_cgb_window_palette_num_extracted_from_bank1_attrs() {
let vram = blank_vram();
let mut bank1 = blank_vram();
bank1[0x1800] = 0x03; let lcdc = 0x30u8;
let result = fetch_window_pixel_cgb(0, 0, &vram, &bank1, lcdc, 7, 0, 0);
assert!(result.is_some());
assert_eq!(result.unwrap().palette_num, 3);
}
#[test]
fn test_cgb_window_bg_priority_extracted_from_bank1_attrs() {
let vram = blank_vram();
let mut bank1 = blank_vram();
bank1[0x1800] = 0x80; let lcdc = 0x30u8;
let result = fetch_window_pixel_cgb(0, 0, &vram, &bank1, lcdc, 7, 0, 0);
assert!(result.unwrap().bg_priority);
}
#[test]
fn test_cgb_window_wx_less_than_7_shifts_tilemap_correctly() {
let mut vram = blank_vram();
let bank1 = blank_vram();
vram[0x1800] = 0; vram[0x0000] = 0x01; vram[0x0001] = 0x00; let lcdc = 0x30u8; let result = fetch_window_pixel_cgb(0, 0, &vram, &bank1, lcdc, 0, 0, 0);
assert!(
result.is_some(),
"window should be visible at x=0 when WX=0"
);
assert_eq!(
result.unwrap().colour_index,
1,
"screen x=0 with WX=0 must map to tile pixel 7 (colour 1)"
);
}
#[test]
fn test_cgb_window_tile_data_read_from_vram_bank1_when_attr_bit3_set() {
let vram = blank_vram();
let mut bank1 = blank_vram();
bank1[0x1800] = 0x08; bank1[0x0000] = 0xFF; bank1[0x0001] = 0x00;
let lcdc = 0x30u8;
let result = fetch_window_pixel_cgb(0, 0, &vram, &bank1, lcdc, 7, 0, 0);
assert!(result.is_some());
let px = result.unwrap();
assert_eq!(px.vram_bank, 1);
assert_eq!(px.colour_index, 1);
}
}