diff --git a/src/main.rs b/src/main.rs index ca553e0..aef4ff3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -151,7 +151,7 @@ impl Memory { } fn set(&mut self, address: Address, data: u8) { - verbose_println!("write addr: {:#X}, data: {:#X}", address, data); + // verbose_println!("write addr: {:#X}, data: {:#X}", address, data); match address { 0x0..0x100 => { @@ -184,7 +184,7 @@ impl Memory { // println!("empty space write: {:#X} to addr {:#X}", data, address); } 0xFF00..0xFF4C => { - verbose_print!("writing to addr {:#X}\r", address); + // verbose_print!("writing to addr {:#X}\r", address); stdout().flush().unwrap(); if address == 0xFF02 && data == 0x81 { diff --git a/src/processor/gpu.rs b/src/processor/gpu.rs index 13ee71f..3d4a4ac 100644 --- a/src/processor/gpu.rs +++ b/src/processor/gpu.rs @@ -1,6 +1,6 @@ use crate::{HEIGHT, WIDTH}; -use super::{clear_bit, set_bit, set_or_clear_bit, CPU}; +use super::{clear_bit, get_bit, set_bit, set_or_clear_bit, CPU}; #[derive(PartialEq)] enum DrawMode { @@ -10,11 +10,69 @@ enum DrawMode { Mode3, } +enum TilemapArea { + T9800, + T9C00, +} + +enum TiledataArea { + D8000, + D8800, +} + +enum ObjSize { + S8x8, + S8x16, +} + +struct LCDC { + enable: bool, + window_tilemap: TilemapArea, + window_enable: bool, + tile_area: TiledataArea, + bg_tilemap: TilemapArea, + obj_size: ObjSize, + obj_enable: bool, + bg_window_enable: bool, +} + +#[derive(Clone, Copy)] +enum Colour { + White, + LightGray, + DarkGray, + Black, +} + +impl Colour { + fn to_rgb(&self) -> u32 { + match self { + Colour::White => Self::from_u8_rgb(255, 255, 255), + Colour::LightGray => Self::from_u8_rgb(190, 190, 190), + Colour::DarkGray => Self::from_u8_rgb(110, 110, 110), + Colour::Black => Self::from_u8_rgb(20, 20, 20), + } + } + + fn from_u8_rgb(r: u8, g: u8, b: u8) -> u32 { + let (r, g, b) = (r as u32, g as u32, b as u32); + (r << 16) | (g << 8) | b + } +} + +#[derive(Clone, Copy)] +struct Palette { + zero: Colour, + one: Colour, + two: Colour, + three: Colour, +} + pub struct GPU { pub buffer: Vec, mode: DrawMode, mode_clock: usize, - scanline: usize, + scanline: u8, } impl Default for GPU { @@ -32,6 +90,9 @@ impl CPU { pub fn advance_gpu_clock(&mut self, steps: u8) { let real_steps = (steps as usize) * 4; self.gpu.mode_clock += real_steps; + + let lcdc = self.get_lcdc(); + match self.gpu.mode { DrawMode::HBlank => { // mode 0: hblank @@ -39,7 +100,7 @@ impl CPU { self.gpu.mode_clock = 0; self.gpu.scanline += 1; if self.gpu.scanline == 143 { - self.enter_vblank(); + self.enter_vblank(&lcdc); } else { self.gpu.mode = DrawMode::Mode2; } @@ -67,29 +128,61 @@ impl CPU { // generate scanline if self.gpu.mode_clock >= 172 { self.gpu.mode_clock = 0; - self.enter_hblank(); + self.enter_hblank(&lcdc); } } } self.set_lcd_status(); } - fn enter_hblank(&mut self) { - self.gpu.mode = DrawMode::HBlank; - self.render_scanline(self.gpu.scanline); + fn get_lcdc(&self) -> LCDC { + let reg = self.memory.get(0xFF40); + LCDC { + enable: get_bit(reg, 7), + window_tilemap: if get_bit(reg, 6) { + TilemapArea::T9C00 + } else { + TilemapArea::T9800 + }, + window_enable: get_bit(reg, 5), + tile_area: if get_bit(reg, 4) { + TiledataArea::D8000 + } else { + TiledataArea::D8800 + }, + bg_tilemap: if get_bit(reg, 3) { + TilemapArea::T9C00 + } else { + TilemapArea::T9800 + }, + obj_size: if get_bit(reg, 2) { + ObjSize::S8x16 + } else { + ObjSize::S8x8 + }, + obj_enable: get_bit(reg, 1), + bg_window_enable: get_bit(reg, 0), + } } - fn enter_vblank(&mut self) { + fn enter_hblank(&mut self, lcdc: &LCDC) { + self.gpu.mode = DrawMode::HBlank; + self.render_scanline(self.gpu.scanline, lcdc); + } + + fn enter_vblank(&mut self, lcdc: &LCDC) { self.gpu.mode = DrawMode::VBlank; + // if lcdc.enable { self.render_window(); self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0)); + // } } fn exit_vblank(&mut self) { self.gpu.mode = DrawMode::Mode2; self.gpu.scanline = 0; - self.memory - .set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 0)); + // self.memory + // .set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 0)); } fn set_lcd_status(&mut self) { @@ -106,9 +199,57 @@ impl CPU { (self.gpu.mode == DrawMode::VBlank) || (self.gpu.mode == DrawMode::Mode3), ); self.memory.set(0xFF41, stat); + self.memory.set(0xFF44, self.gpu.scanline); + // println!("set scanline to {}", self.memory.get(0xFF44)); } - fn render_scanline(&mut self, _scanline: usize) {} + fn render_scanline(&mut self, scanline: u8, lcdc: &LCDC) { + if lcdc.bg_window_enable { + self.render_scanline_bg(scanline, lcdc); + if lcdc.window_enable { + self.render_scanline_window(scanline, lcdc); + } + } + if lcdc.obj_enable { + self.render_scanline_obj(scanline, lcdc); + } + } + + fn render_scanline_bg(&mut self, scanline: u8, lcdc: &LCDC) { + let scroll_y = self.memory.get(0xFF42); + let scroll_x = self.memory.get(0xFF43); + let palette = byte_to_palette(self.memory.get(0xFF47)); + self.render_tiles(scanline, &lcdc.bg_tilemap, palette); + } + + fn render_scanline_window(&mut self, _scanline: u8, _lcdc: &LCDC) { + let pos_y = self.memory.get(0xFF4A); + // subtracting 7 to get the Real Number... + let pos_x = self.memory.get(0xFF4B).wrapping_sub(7); + if pos_y < 143 && pos_x < 166 { + // within range!! render here + } + } + + fn render_scanline_obj(&mut self, _scanline: u8, _lcdc: &LCDC) {} + + fn render_tiles(&mut self, scanline: u8, tilemap: &TilemapArea, palette: Palette) { + let tile_row = (scanline as usize) / 8; + let row_addr = (tile_row * 32) as u16 + get_tilemap_offset(tilemap); + for x in 0..32 { + let lsbs = self.memory.get(row_addr + (x * 2)); + let msbs = self.memory.get(row_addr + (x * 2) + 1); + for px_x in 0..8 { + let lsb = get_bit(lsbs, px_x); + let msb = get_bit(msbs, px_x); + let colour = bits_to_mapped_colour(lsb, msb, palette); + let x_coord = ((x * 8) + (px_x as u16)) as usize; + if x_coord < WIDTH { + self.gpu.buffer[(scanline as usize * WIDTH) + x_coord] = colour.to_rgb(); + } + } + } + } fn render_window(&mut self) { self.window @@ -116,3 +257,37 @@ impl CPU { .unwrap(); } } + +fn get_tilemap_offset(tilemap: &TilemapArea) -> u16 { + match tilemap { + TilemapArea::T9800 => 0x9800, + TilemapArea::T9C00 => 0x9C00, + } +} + +fn bits_to_mapped_colour(lsb: bool, msb: bool, palette: Palette) -> Colour { + match (lsb, msb) { + (true, true) => palette.three, + (true, false) => palette.one, + (false, true) => palette.two, + (false, false) => palette.zero, + } +} + +fn byte_to_palette(byte: u8) -> Palette { + Palette { + zero: bits_to_colour(get_bit(byte, 0), get_bit(byte, 1)), + one: bits_to_colour(get_bit(byte, 2), get_bit(byte, 3)), + two: bits_to_colour(get_bit(byte, 4), get_bit(byte, 5)), + three: bits_to_colour(get_bit(byte, 6), get_bit(byte, 7)), + } +} + +fn bits_to_colour(first: bool, second: bool) -> Colour { + match (first, second) { + (true, true) => Colour::Black, + (true, false) => Colour::DarkGray, + (false, true) => Colour::LightGray, + (false, false) => Colour::White, + } +}