use super::background;
use super::registers::Registers;
use super::screen_buffer::ScreenBuffer;
use super::sprites;
use super::window;
const DMG_GREY: [u8; 4] = [0xFF, 0xAA, 0x55, 0x00];
pub fn palette_lookup(palette_reg: u8, colour_index: u8) -> u8 {
let shade = (palette_reg >> (colour_index * 2)) & 0x03;
DMG_GREY[shade as usize]
}
pub fn render_scanline(
scanline: u8,
vram: &[u8; 0x2000],
oam: &[u8; 0xA0],
registers: &Registers,
window_line: &mut u8,
screen_buffer: &mut ScreenBuffer,
) {
let lcdc = registers.lcdc;
let bg_window_enabled = lcdc & 0x01 != 0;
let obj_enabled = lcdc & 0x02 != 0;
let win_enabled = lcdc & 0x20 != 0;
let sprite_indices = if obj_enabled {
sprites::scan_oam_line(scanline, oam, lcdc)
} else {
Vec::new()
};
let mut window_active = false;
for x in 0..ScreenBuffer::WIDTH {
let bg_idx = if bg_window_enabled {
background::fetch_bg_pixel(x, scanline, vram, lcdc, registers.scx, registers.scy)
} else {
0 };
let bw_idx = if win_enabled {
match window::fetch_window_pixel(
x,
scanline,
vram,
lcdc,
registers.wx,
registers.wy,
*window_line,
) {
Some(idx) => {
window_active = true;
idx
}
None => bg_idx,
}
} else {
bg_idx
};
let sprite_px = sprites::fetch_sprite_pixel(x, scanline, &sprite_indices, oam, vram, lcdc);
let (final_idx, is_sprite, sprite_pal) = if let Some(sp) = sprite_px {
if sp.bg_priority && bw_idx != 0 {
(bw_idx, false, 0u8)
} else {
(sp.colour_index, true, sp.palette)
}
} else {
(bw_idx, false, 0u8)
};
let grey = if is_sprite {
let pal = if sprite_pal == 0 {
registers.obp0
} else {
registers.obp1
};
palette_lookup(pal, final_idx)
} else {
palette_lookup(registers.bgp, final_idx)
};
screen_buffer.set_pixel(x, scanline as u32, grey, grey, grey);
}
if window_active {
*window_line = window_line.wrapping_add(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_registers() -> Registers {
Registers::new()
}
fn blank_vram() -> [u8; 0x2000] {
[0u8; 0x2000]
}
fn blank_oam() -> [u8; 0xA0] {
[0u8; 0xA0]
}
#[test]
fn test_palette_index_0_maps_to_white_with_default_bgp() {
assert_eq!(palette_lookup(0xE4, 0), DMG_GREY[0]); }
#[test]
fn test_palette_index_3_maps_to_black_with_default_bgp() {
assert_eq!(palette_lookup(0xE4, 3), DMG_GREY[3]); }
#[test]
fn test_palette_inverted_bgp_maps_0_to_black() {
assert_eq!(palette_lookup(0x1B, 0), DMG_GREY[3]);
}
#[test]
fn test_palette_all_white_bgp_maps_every_index_to_white() {
for i in 0..4 {
assert_eq!(palette_lookup(0x00, i), DMG_GREY[0]);
}
}
#[test]
fn test_render_scanline_fills_160_pixels() {
let vram = blank_vram();
let oam = blank_oam();
let regs = default_registers();
let mut sb = ScreenBuffer::new();
let mut wl = 0u8;
render_scanline(0, &vram, &oam, ®s, &mut wl, &mut sb);
for x in 0..160 {
let (r, g, b) = sb.get_pixel(x, 0);
assert_eq!(r, g, "pixel ({x},0) r≠g");
assert_eq!(g, b, "pixel ({x},0) g≠b");
}
}
#[test]
fn test_render_scanline_with_inverted_bgp_paints_black() {
let vram = blank_vram();
let oam = blank_oam();
let mut regs = default_registers();
regs.bgp = 0xFF; let mut sb = ScreenBuffer::new();
let mut wl = 0u8;
render_scanline(0, &vram, &oam, ®s, &mut wl, &mut sb);
for x in 0..160 {
assert_eq!(
sb.get_pixel(x, 0),
(0x00, 0x00, 0x00),
"pixel ({x},0) should be black"
);
}
}
#[test]
fn test_render_scanline_bg_disabled_paints_white() {
let vram = blank_vram();
let oam = blank_oam();
let mut regs = default_registers();
regs.lcdc = 0x80; regs.bgp = 0xE4; let mut sb = ScreenBuffer::new();
let mut wl = 0u8;
render_scanline(0, &vram, &oam, ®s, &mut wl, &mut sb);
for x in 0..160 {
assert_eq!(
sb.get_pixel(x, 0),
(0xFF, 0xFF, 0xFF),
"pixel ({x},0) should be white when BG disabled"
);
}
}
}