extern crate sdl2; extern crate libc; // Internal ram size const VRAM_SIZE: usize = 0x2000; // Timing values const TICKS_END_SCANLINE: u16 = 80; const TICKS_END_READMODE: u16 = 172; const TICKS_END_HBLANK: u16 = 204; const TICKS_END_VBLANK: u16 = 456; // Display size const GB_PIXELS_X: u16 = 160; const GB_PIXELS_Y: u16 = 144; const SCALE: u16 = 4; // Control flags const CTRL_LCD_DISPLAY_ENABLE: u8 = 1 << 7; const CTRL_TILE_MAP_SELECT: u8 = 1 << 6; const CTRL_WND_DISPLAY_ENABLE: u8 = 1 << 5; const CTRL_BG_WINDOW_TILE_DATA_SELECT: u8 = 1 << 4; const CTRL_BG_TILE_MAP_SELECT: u8 = 1 << 3; const CTRL_BG_SPRITE_SIZE: u8 = 1 << 2; const CTRL_BG_SPRITE_ENABLE: u8 = 1 << 1; const CTRL_BG_DISPLAY: u8 = 1 << 0; #[derive(Debug)] enum DisplayMode { Scanline, Readmode, HBlank, VBlank, } impl Default for DisplayMode { fn default() -> DisplayMode { DisplayMode::Scanline } } pub struct Display { control: u8, status: u8, background_palette: u8, object_palette_0: u8, object_palette_1: u8, scrollx: u8, scrolly: u8, windowx: u8, windowy: u8, curline: u8, vram: Box<[u8]>, current_ticks: u16, current_mode: DisplayMode, // TODO // For creating a window: // sdl_context: sdl2::Sdl, // video_context: sdl2::VideoSubsystem, // window: sdl2::video::Window, renderer: sdl2::render::Renderer<'static>, } impl Display { pub fn new() -> Display { let sdl_ctx = sdl2::init().unwrap(); let video_ctx = sdl_ctx.video().unwrap(); let wnd = video_ctx.window("RustBoy", (GB_PIXELS_X * SCALE) as u32, (GB_PIXELS_Y * SCALE) as u32).position_centered().build().expect("Failed to create window :<"); let renderer = wnd.renderer().build().expect("Could not build renderer"); Display { control: 0, status: 0, background_palette: 0, object_palette_0: 0, object_palette_1: 0, scrollx: 0, scrolly: 0, windowx: 0, windowy: 0, curline: 0, current_ticks: 0, current_mode: DisplayMode::default(), vram: vec![0; VRAM_SIZE].into_boxed_slice(), // sdl_context: sdl_ctx, // video_context: video_ctx, // window: wnd, renderer: renderer } } fn set_pixel(&mut self, x: u8, y: u8, color: sdl2::pixels::Color) { self.renderer.set_draw_color(color); self.renderer.fill_rect(sdl2::rect::Rect::new((x as i32) * SCALE as i32, (y as i32) * SCALE as i32, SCALE as u32, SCALE as u32)); } pub fn write_byte(&mut self, addr: u16, val: u8) { match addr { 0x8000 ... 0x9FFF => self.vram[(addr - 0x8000) as usize] = val, 0xFF40 => self.control = val, 0xFF41 => self.status = val, 0xFF42 => self.scrolly = val, 0xFF43 => self.scrollx = val, 0xFF44 => self.curline = 0, 0xFF47 => self.background_palette = val, 0xFF48 => self.object_palette_0 = val, 0xFF49 => self.object_palette_1 = val, 0xFF4A => self.windowy = val, 0xFF4B => self.windowx = val, _ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr), } } pub fn read_byte(&self, addr: u16) -> u8 { match addr { 0x8000 ... 0x9FFF => self.vram[(addr - 0x8000) as usize], 0xFF40 => self.control, 0xFF41 => self.status, 0xFF42 => self.scrolly, 0xFF43 => self.scrollx, 0xFF44 => self.curline, 0xFF47 => self.background_palette, 0xFF48 => self.object_palette_0, 0xFF49 => self.object_palette_1, 0xFF4A => self.windowy, 0xFF4B => self.windowx, _ => panic!("Display: Read from {:04X} unsupported", addr), } } // Do we want to have a time delta here? pub fn tick(&mut self, ticks: u16) { self.current_ticks += ticks; match self.current_mode { DisplayMode::Scanline => { if self.current_ticks > TICKS_END_SCANLINE { self.current_ticks = 0; self.current_mode = DisplayMode::Readmode; } }, DisplayMode::Readmode => { if self.current_ticks > TICKS_END_READMODE { self.current_ticks = 0; self.current_mode = DisplayMode::HBlank; self.renderscan(); } }, DisplayMode::HBlank => { if self.current_ticks > TICKS_END_HBLANK { self.current_ticks = 0; self.curline += 1; if self.curline == 143 { self.current_mode = DisplayMode::VBlank; } else { self.current_mode = DisplayMode::Scanline; } } }, DisplayMode::VBlank => { if self.current_ticks > TICKS_END_VBLANK { self.current_ticks = 0; self.curline += 1; if self.curline > 153 { self.renderer.present(); self.renderer.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0)); self.renderer.clear(); self.current_mode = DisplayMode::Scanline; self.curline = 0; } } } } } fn renderscan(&mut self) { // Points to the background map offset to use. let background_map: usize; // verify! if self.control & CTRL_BG_TILE_MAP_SELECT > 0 { background_map = 0x1C00; } else { background_map = 0x1800; } // Those are pixel units, not tile units. let map_offset_y: u8 = self.scrolly; let map_offset_x: u8 = self.scrollx; let render_y: u8 = self.curline; if self.control & CTRL_BG_SPRITE_ENABLE > 0 { panic!("Sprites not supported"); } // Render background if self.control & CTRL_BG_DISPLAY > 0 { // Draw borders first. for t_x in 0 .. 160/8 { self.renderer.set_draw_color(sdl2::pixels::Color::RGB(0xFF, 10, 0xFF)); // Awesome effects: /* let RX: i32 = (t_x as i32)*8 + map_offset_x as i32; let RY: i32 = (render_y as i32) & 0xFFFFFFF8 + map_offset_y as i32; let TS: u32 = 8u32 * (SCALE as u32); */ let RX: i32 = (t_x as i32)*8 + map_offset_x as i32; let RY: i32 = ((render_y as u32) & 0xFFFFFFF8) as i32 - map_offset_y as i32; let TS: u32 = 8u32 * (SCALE as u32); self.renderer.draw_rect( sdl2::rect::Rect::new(RX * SCALE as i32, RY * SCALE as i32, TS, TS) ); } // Render pixels (20 tiles) for render_x in 0 .. 160 { // Absolute render coordinates let render_abs_x = map_offset_x.wrapping_add(render_x); let render_abs_y = map_offset_y.wrapping_add(render_y); let tile_index_x: u8 = render_abs_x / 8; let tile_offset_x: u8 = render_abs_x % 8; let tile_index_y: u8 = render_abs_y / 8; let tile_offset_y: u8 = render_abs_y % 8; let vram_offset: usize = background_map + (tile_index_y as usize) * 32 + tile_index_x as usize; // Obtain tile ID in this area let mut tile_id = self.vram[vram_offset]; // println!("vram offset: {:04X}", vram_offset); if tile_id != 0 { // println!("Drawing tile {:02X}", tile_id); // print!("{:02X} ", tile_id); } // Obtain tile information let tile_base_addr: usize; if self.control & CTRL_BG_WINDOW_TILE_DATA_SELECT > 0 { tile_base_addr = 0x0000; } else { tile_base_addr = 0x0800; // This set goes from -127 to 127. let s_tid: i8 = tile_id as i8; tile_id = (128u8 as i8 + s_tid) as u8; panic!("OH MY GOD, this wasn't tested yet"); } let tile_base_addr: usize = tile_base_addr + (tile_id as usize) * 16; let tile_line_1 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2]; let tile_line_2 = self.vram[tile_base_addr + (tile_offset_y as usize) * 2 + 1]; // Get the correct bit let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0; let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0; let mut factor = 0; if b1 { factor += 64; } if b2 { factor += 128; } // Draw stuff. We're currently only in monochrome mode self.set_pixel(render_x, render_y, sdl2::pixels::Color::RGB(factor, factor, factor)); } } } pub fn vblank_interrupt(&mut self) { self.curline += 1; if self.curline > 153 { self.curline = 0; } } pub fn controller_interrupt(&self) { } }