feat: ppu sprites

This commit is contained in:
EliseZeroTwo 2021-11-24 19:25:13 +01:00
parent 5fa12cb8eb
commit 4abfe1ae90
No known key found for this signature in database
GPG key ID: E6D56A6F7B7991DE

View file

@ -32,7 +32,7 @@ pub enum PPUMode {
TransferringData,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
White,
LGray,
@ -93,6 +93,36 @@ impl Color {
}
}
#[derive(Debug)]
pub struct OAMEntry {
pub y: u8,
pub x: u8,
pub tile_idx: u8,
pub flags: u8,
}
impl OAMEntry {
pub fn parse(entry: [u8; 4]) -> Self {
Self { y: entry[0], x: entry[1], tile_idx: entry[2], flags: entry[3] }
}
pub fn covered_by_bg_window(&self) -> bool {
(self.flags >> 7) & 0b1 == 1
}
pub fn y_flip(&self) -> bool {
(self.flags >> 6) & 0b1 == 1
}
pub fn x_flip(&self) -> bool {
(self.flags >> 5) & 0b1 == 1
}
pub fn palette_number(&self) -> bool {
(self.flags >> 4) & 0b1 == 1
}
}
impl PPUMode {
pub fn mode_flag(&self) -> u8 {
match self {
@ -170,22 +200,116 @@ impl Ppu {
}
fn draw_line(&mut self) {
let scrolled_y = self.ly.overflowing_add(self.scy).0 as usize;
for pixel_idx in 0..FB_WIDTH as u8 {
let scrolled_x = pixel_idx.overflowing_add(self.scx).0 as usize;
let scrolled_y = self.ly.overflowing_add(self.scy).0 as usize;
// BG
let tilemap_idx = scrolled_x / 8 + ((scrolled_y as usize / 8) * 32);
let tilemap_value = self.read_tile_map()[tilemap_idx];
let color = Self::parse_tile_color(
self.read_tile(tilemap_value),
self.read_bg_win_tile(tilemap_value),
scrolled_x % 8,
scrolled_y % 8,
);
let dest_idx_base = ((self.scy as usize * FB_WIDTH as usize) + pixel_idx as usize) * 4;
let dest_idx_base = (((self.ly as usize + self.scy as usize) * FB_WIDTH as usize)
+ pixel_idx as usize)
* 4;
for (idx, byte) in color.rgba().iter().enumerate() {
self.framebuffer[dest_idx_base + idx] = *byte;
}
}
// Sprite
let mut found_sprites = 0;
let mut sprite_line = [0u8; 256 * 4];
let sprite_height = if (self.lcdc >> 2) & 0b1 == 1 { 16 } else { 8 };
for x in 0..40 {
if found_sprites >= 10 {
break;
}
let oam_offset = x * 4;
let entry = OAMEntry::parse([
self.oam[oam_offset],
self.oam[oam_offset + 1],
self.oam[oam_offset + 2],
self.oam[oam_offset + 3],
]);
let mut base = entry.y.overflowing_sub(sprite_height).0;
let mut in_range = None;
for x in 0..sprite_height {
if base as usize == scrolled_y {
in_range = Some(x);
found_sprites += 1;
break;
}
base = base.overflowing_add(1).0;
}
if let Some(mut tile_y_idx) = in_range {
let is_second_tile = tile_y_idx >= 8;
if is_second_tile {
tile_y_idx -= 8;
}
if entry.y_flip() {
tile_y_idx = 8 - tile_y_idx;
}
let tile_idx =
if is_second_tile { entry.tile_idx | 1 } else { entry.tile_idx & 0xFE };
for x in 0..8 {
let fb_x = entry.x.overflowing_sub(8 - x).0;
let sprite_line_base = fb_x as usize * 4;
let tile_x_idx = if entry.x_flip() { 8 - x } else { x };
let color = Self::parse_tile_color(
self.read_obj_tile(tile_idx),
tile_x_idx as usize,
tile_y_idx as usize,
);
let bg_fb_idx = (((self.ly as usize + self.scy as usize) * FB_WIDTH as usize)
+ fb_x as usize) * 4;
let ok_to_draw = if entry.covered_by_bg_window() {
let rgba = [
self.framebuffer[bg_fb_idx],
self.framebuffer[bg_fb_idx + 1],
self.framebuffer[bg_fb_idx + 2],
self.framebuffer[bg_fb_idx + 3],
];
&rgba != Color::Black.rgba()
} else {
true
};
if ok_to_draw {
for (idx, byte) in color.rgba().iter().enumerate() {
sprite_line[sprite_line_base + idx] = *byte;
}
}
}
}
}
for x in (self.scx as usize)..(self.scx as usize + FB_WIDTH as usize) {
let x = x % FB_WIDTH as usize;
let base = x * 4;
self.sprite_framebuffer[base] = sprite_line[x];
self.sprite_framebuffer[base + 1] = sprite_line[x + 1];
self.sprite_framebuffer[base + 2] = sprite_line[x + 2];
self.sprite_framebuffer[base + 3] = sprite_line[x + 3];
}
}
fn parse_tile_color(tile: &[u8], x: usize, y: usize) -> Color {
@ -346,13 +470,17 @@ impl Ppu {
self.stat = value & 0b0111_1000;
}
pub fn read_tile(&self, obj: u8) -> &[u8] {
pub fn read_obj_tile(&self, idx: u8) -> &[u8] {
&self.vram[idx as usize * 16..((idx as usize + 1) * 16)]
}
pub fn read_bg_win_tile(&self, idx: u8) -> &[u8] {
if (self.lcdc >> 4) & 0b1 == 1 {
&self.vram[obj as usize * 16..((obj as usize + 1) * 16)]
} else if obj < 128 {
&self.vram[0x1000 + (obj as usize * 16)..0x1000 + ((obj as usize + 1) * 16)]
&self.vram[idx as usize * 16..((idx as usize + 1) * 16)]
} else if idx < 128 {
&self.vram[0x1000 + (idx as usize * 16)..0x1000 + ((idx as usize + 1) * 16)]
} else {
let adjusted_obj = obj - 128;
let adjusted_obj = idx - 128;
&self.vram
[0x800 + (adjusted_obj as usize * 16)..0x800 + ((adjusted_obj as usize + 1) * 16)]
}