pub fn fetch_bg_pixel(x: u32, scanline: u8, vram: &[u8; 0x2000], lcdc: u8, scx: u8, scy: u8) -> u8 {
let bg_x = scx.wrapping_add(x as u8);
let bg_y = scy.wrapping_add(scanline);
let map_base: usize = if lcdc & 0x08 != 0 { 0x1C00 } else { 0x1800 };
let tile_col = (bg_x / 8) as usize;
let tile_row = (bg_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 = (bg_y % 8) as usize;
let bit = 7 - (bg_x % 8);
let addr = tile_data_start + row_in_tile * 2;
let low = vram[addr];
let high = vram[addr + 1];
((high >> bit) & 1) << 1 | ((low >> bit) & 1)
}
#[derive(Debug, Clone, Copy)]
pub struct BgPixelCgb {
pub colour_index: u8,
pub palette_num: u8,
pub vram_bank: u8,
pub bg_priority: bool,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct CgbPixelFetch {
pub x: u32,
pub scanline: u8,
pub scx: u8,
pub fine_scx: u8,
pub scy_map: u8,
pub scy_data_low: u8,
pub scy_data_high: u8,
pub lcdc: u8,
}
pub fn fetch_bg_pixel_cgb(
x: u32,
scanline: u8,
vram: &[u8; 0x2000],
vram_bank1: &[u8; 0x2000],
lcdc: u8,
scx: u8,
scy: u8,
) -> BgPixelCgb {
fetch_bg_pixel_cgb_with_fine_scx(
vram,
vram_bank1,
CgbPixelFetch {
x,
scanline,
scx,
fine_scx: scx,
scy_map: scy,
scy_data_low: scy,
scy_data_high: scy,
lcdc,
},
)
}
pub(super) fn fetch_bg_pixel_cgb_with_fine_scx(
vram: &[u8; 0x2000],
vram_bank1: &[u8; 0x2000],
fetch: CgbPixelFetch,
) -> BgPixelCgb {
let bg_x = fetch.scx.wrapping_add(fetch.x as u8);
let bg_y_map = fetch.scy_map.wrapping_add(fetch.scanline);
let bg_y_data_low = fetch.scy_data_low.wrapping_add(fetch.scanline);
let bg_y_data_high = fetch.scy_data_high.wrapping_add(fetch.scanline);
let map_base: usize = if fetch.lcdc & 0x08 != 0 {
0x1C00
} else {
0x1800
};
let tile_col = (bg_x / 8) as usize;
let tile_row = (bg_y_map / 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 fetch.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_low = (bg_y_data_low % 8) as usize;
let mut row_in_tile_high = (bg_y_data_high % 8) as usize;
if y_flip {
row_in_tile_low = 7 - row_in_tile_low;
row_in_tile_high = 7 - row_in_tile_high;
}
let pixel_in_tile = fetch.fine_scx.wrapping_add(fetch.x as u8) % 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 low = tile_vram[tile_data_start + row_in_tile_low * 2];
let high = tile_vram[tile_data_start + row_in_tile_high * 2 + 1];
let colour_index = ((high >> bit) & 1) << 1 | ((low >> bit) & 1);
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_blank_vram_returns_colour_index_0() {
let vram = blank_vram();
let idx = fetch_bg_pixel(0, 0, &vram, 0x91, 0, 0);
assert_eq!(idx, 0);
}
#[test]
fn test_tile_data_colour_bits_are_fetched_correctly() {
let mut vram = blank_vram();
vram[0x0010] = 0xFF; vram[0x0011] = 0x00; vram[0x1800] = 0x01;
let lcdc = 0x91u8; let idx = fetch_bg_pixel(0, 0, &vram, lcdc, 0, 0);
assert_eq!(idx, 1);
}
#[test]
fn test_scx_shifts_bg_map_lookup() {
let mut vram = blank_vram();
vram[0x0020] = 0xFF;
vram[0x0021] = 0xFF;
vram[0x1801] = 0x02;
let lcdc = 0x91u8;
let idx = fetch_bg_pixel(0, 0, &vram, lcdc, 8, 0);
assert_eq!(idx, 3);
}
#[test]
fn test_scy_shifts_bg_map_row_lookup() {
let mut vram = blank_vram();
vram[0x0010] = 0x00;
vram[0x0011] = 0xFF;
vram[0x1820] = 0x01;
let lcdc = 0x91u8;
let idx = fetch_bg_pixel(0, 0, &vram, lcdc, 0, 8);
assert_eq!(idx, 2);
}
#[test]
fn test_cgb_bg_palette_num_extracted_from_attrs() {
let vram = blank_vram();
let mut bank1 = blank_vram();
bank1[0x1800] = 0x05; let px = fetch_bg_pixel_cgb(0, 0, &vram, &bank1, 0x91, 0, 0);
assert_eq!(px.palette_num, 5);
}
#[test]
fn test_cgb_bg_priority_flag_extracted_from_attrs() {
let vram = blank_vram();
let mut bank1 = blank_vram();
bank1[0x1800] = 0x80; let px = fetch_bg_pixel_cgb(0, 0, &vram, &bank1, 0x91, 0, 0);
assert!(px.bg_priority);
bank1[0x1800] = 0x00;
let px2 = fetch_bg_pixel_cgb(0, 0, &vram, &bank1, 0x91, 0, 0);
assert!(!px2.bg_priority);
}
#[test]
fn test_cgb_bg_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 px = fetch_bg_pixel_cgb(0, 0, &vram, &bank1, 0x91, 0, 0);
assert_eq!(px.colour_index, 1);
assert_eq!(px.vram_bank, 1);
}
#[test]
fn test_cgb_bg_x_flip_reverses_pixel_order() {
let mut vram = blank_vram();
let mut bank1 = blank_vram();
vram[0x0000] = 0x01; let px_no_flip = fetch_bg_pixel_cgb(0, 0, &vram, &bank1, 0x91, 0, 0);
assert_eq!(px_no_flip.colour_index, 0);
bank1[0x1800] = 0x20; let px_x_flip = fetch_bg_pixel_cgb(0, 0, &vram, &bank1, 0x91, 0, 0);
assert_eq!(px_x_flip.colour_index, 1);
}
#[test]
fn test_cgb_bg_y_flip_reverses_row_order() {
let mut vram = blank_vram();
let mut bank1 = blank_vram();
vram[0x000E] = 0xFF; vram[0x000F] = 0x00;
let px_no = fetch_bg_pixel_cgb(0, 0, &vram, &bank1, 0x91, 0, 0);
assert_eq!(px_no.colour_index, 0);
bank1[0x1800] = 0x40; let px_yf = fetch_bg_pixel_cgb(0, 0, &vram, &bank1, 0x91, 0, 0);
assert_eq!(px_yf.colour_index, 1);
}
}