extern crate libc; extern crate sdl2; use std::io::Write; // Internal ram size const VRAM_SIZE: usize = 0x2000; // OAM size const OAM_SIZE: usize = (0xFE9F - 0xFE00) + 1; // 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: usize = 160; const GB_PIXELS_Y: usize = 144; const SCALE: usize = 4; const WND_RES_X: usize = GB_PIXELS_X * SCALE; const WND_RES_Y: usize = GB_PIXELS_Y * SCALE; // Control flags const CTRL_LCD_DISPLAY_ENABLE: u8 = 1 << 7; const CTRL_WND_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; // Status flags const STAT_LYC_LC_COINCIDENCE_INT: u8 = 1 << 6; const STAT_MODE_OAM_INT: u8 = 1 << 5; const STAT_MODE_VBLANK_INT: u8 = 1 << 4; const STAT_MODE_HBLANK_INT: u8 = 1 << 3; // Sprite flags const SPRITE_OBJ_BG_PRIORITY: u8 = 1 << 7; const SPRITE_Y_FLIP: u8 = 1 << 6; const SPRITE_X_FLIP: u8 = 1 << 5; //const SPRITE_PALETTE_NO: u8 = 1 << 4; // NonCGB only const SPRITE_TILE_VRAM_BANK: u8 = 1 << 3; // CGB only // Display color /* const MONOCHROME_PALETTE: &'static [[f64; 3]; 4] = &[ [0.605, 0.734, 0.059], [0.543, 0.672, 0.059], [0.188, 0.383, 0.188], [0.059, 0.219, 0.059], ]; const MONOCHROME_PALETTE: &'static [[u8; 3]; 4] = &[ [255, 255, 255], [200, 200, 200], [125, 125, 12], [50, 50, 50], ]; */ #[derive(Copy, Clone)] struct CgbPalette([u8; 8]); impl CgbPalette { fn get_color(self, n: usize) -> sdl2::pixels::Color { if n == 0 { return sdl2::pixels::Color::RGB(255, 255, 255); } let v = ((self.0[2 * n + 1] as u16) << 8) | (self.0[2 * n] as u16); let r = (v & 0b1_1111) as u8; let g = ((v >> 5) & 0b1_1111) as u8; let b = ((v >> 10) & 0b1_1111) as u8; // According to some code: // Real colors: let r = r as u16; let g = g as u16; let b = b as u16; let mapped_r = ((r * 13 + g * 2 + b) >> 1) as u8; let mapped_g = ((g * 3 + b) << 1) as u8; let mapped_b = ((r * 3 + g * 2 + b * 11) >> 1) as u8; sdl2::pixels::Color::RGB(mapped_r, mapped_g, mapped_b) } } struct BgMapAttributes(u8); impl BgMapAttributes { fn palette_number(&self) -> usize { (self.0 & 0b111) as usize } fn vram_bank_number(&self) -> usize { ((self.0 >> 3) & 1) as usize } fn horizontal_flip(&self) -> bool { (self.0 >> 5) != 0 } fn vertical_flip(&self) -> bool { (self.0 >> 6) != 0 } } #[derive(Debug, Copy, Clone)] enum PixelOrigin { Empty, Background, Window, Sprite, } #[derive(Copy, Clone)] struct Pixel { origin: PixelOrigin, color: sdl2::pixels::Color, } impl Default for Pixel { fn default() -> Pixel { Pixel { origin: PixelOrigin::Empty, color: sdl2::pixels::Color::RGB(255, 0, 0), } } } #[derive(Debug)] enum DisplayMode { ReadOAMMemory, ReadFullMemory, HBlank, VBlank, } impl Default for DisplayMode { fn default() -> DisplayMode { DisplayMode::HBlank } } struct Sprite { x: u8, y: u8, tile: u8, flags: u8, } impl Sprite { fn is_hidden(&self) -> bool { self.x == 0 || self.y == 0 } fn is_foreground(&self) -> bool { (self.flags & SPRITE_OBJ_BG_PRIORITY) == 0 } fn is_x_flipped(&self) -> bool { (self.flags & SPRITE_X_FLIP) == SPRITE_X_FLIP } fn is_y_flipped(&self) -> bool { (self.flags & SPRITE_Y_FLIP) == SPRITE_Y_FLIP } fn palette(&self) -> u8 { // GB /* if (self.flags & SPRITE_PALETTE_NO) == 0 { 0 } else { 1 } */ // GBC self.flags & 0b111 } // GBC only fn vram_bank(&self) -> u8 { if (self.flags & SPRITE_TILE_VRAM_BANK) == 0 { 0 } else { 1 } } } pub struct Display { control: u8, status: u8, background_palette: u8, // Only 0 and 1 for GB object_palette: [u8; 2], scrollx: u8, scrolly: u8, windowx: u8, windowy: u8, curline: u8, lyc: u8, vram0: Box<[u8]>, vram1: Box<[u8]>, oam: Box<[u8]>, vram_bank: u8, current_ticks: u16, current_mode: DisplayMode, renderer: sdl2::render::Canvas, pub event_pump: sdl2::EventPump, vblank_interrupt: bool, stat_interrupt: bool, pixels: [Pixel; GB_PIXELS_X * GB_PIXELS_Y], frameskip: u8, frame_no: u8, // GBC: background_palette_autoinc: bool, background_palette_index: u8, background_palette_cgb: [CgbPalette; 0x40 / 8], object_palette_autoinc: bool, object_palette_index: u8, object_palette_cgb: [CgbPalette; 0x40 / 8], } 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", WND_RES_X as u32, WND_RES_Y as u32) .position_centered() .build() .expect("Failed to create window :<"); let renderer = wnd.into_canvas().build().expect("Could not build renderer"); let event_pump = sdl_ctx.event_pump().expect("Getting event pump failed"); let pixels = [Pixel::default(); (GB_PIXELS_X as usize) * (GB_PIXELS_Y as usize)]; Display { control: 0, status: 0, background_palette: 0, object_palette: [0u8; 2], scrollx: 0, scrolly: 0, windowx: 0, windowy: 0, curline: 0, lyc: 0, current_ticks: 0, current_mode: DisplayMode::default(), vram0: vec![0; VRAM_SIZE].into_boxed_slice(), vram1: vec![0; VRAM_SIZE].into_boxed_slice(), vram_bank: 0, oam: vec![0; OAM_SIZE].into_boxed_slice(), renderer, event_pump, vblank_interrupt: false, stat_interrupt: false, pixels, frameskip: 0, frame_no: 0, background_palette_autoinc: false, background_palette_index: 0, background_palette_cgb: [CgbPalette([0u8; 2 * 4]); 0x40 / 8], object_palette_autoinc: false, object_palette_index: 0, object_palette_cgb: [CgbPalette([0u8; 2 * 4]); 0x40 / 8], } } fn set_pixel(&mut self, x: u8, y: u8, color: sdl2::pixels::Color, origin: PixelOrigin) { let p = &mut self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X]; p.color = color; p.origin = origin; } fn get_pixel_origin(&self, x: u8, y: u8) -> PixelOrigin { self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X].origin } fn render_screen(&mut self) { for y in 0..GB_PIXELS_Y { for x in 0..GB_PIXELS_X { let f = &mut self.pixels[(x as usize) + (y as usize) * GB_PIXELS_X]; self.renderer.set_draw_color(f.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, )) .expect("Rendering failed"); // Clear origin after rendering f.origin = PixelOrigin::Empty; } } self.renderer .set_draw_color(sdl2::pixels::Color::RGB(255, 0, 255)); self.renderer .draw_rect(sdl2::rect::Rect::new( 0, 0, (GB_PIXELS_X * SCALE) as u32, (GB_PIXELS_Y * SCALE) as u32, )) .expect("Rendering failed"); } pub fn vblank_interrupt(&mut self) -> bool { // Returns whether or not a vblank interrupt should be done // Yes, this is polling, and yes, this sucks. if self.vblank_interrupt { self.vblank_interrupt = false; true } else { false } } pub fn stat_interrupt(&mut self) -> bool { if self.stat_interrupt { self.stat_interrupt = false; true } else { false } } pub fn write_byte(&mut self, addr: u16, val: u8) { match addr { 0x8000..=0x9FFF => { if (self.vram_bank & 1) == 0 { self.vram0[(addr - 0x8000) as usize] = val } else { self.vram1[(addr - 0x8000) as usize] = val } } 0xFE00..=0xFE9F => self.oam[(addr - 0xFE00) as usize] = val, 0xFF40 => self.control = val, 0xFF41 => self.status = val, 0xFF42 => self.scrolly = val, 0xFF43 => self.scrollx = val, 0xFF44 => self.curline = 0, 0xFF45 => self.lyc = val, // GB classic 0xFF47 => self.background_palette = val, 0xFF48 => self.object_palette[0] = val, 0xFF49 => self.object_palette[1] = val, // GBC 0xFF68 => { self.background_palette_index = val & 0b0111_1111; self.background_palette_autoinc = (val >> 7) != 0; } 0xFF69 => { let idx = self.background_palette_index as usize; if idx < 64 { self.background_palette_cgb[idx / 8].0[idx % 8] = val; } else { panic!("OOB palette w/ autoinc"); } if self.background_palette_autoinc { self.background_palette_index += 1; } } 0xFF6A => { self.object_palette_index = val & 0b0111_1111; self.object_palette_autoinc = (val >> 7) != 0; } 0xFF6B => { let idx = self.object_palette_index as usize; if idx < 64 { self.object_palette_cgb[idx / 8].0[idx % 8] = val; } else { panic!("OOB obj palette w/ autoinc"); } if self.object_palette_autoinc { self.object_palette_index += 1; } } 0xFF4A => { if self.windowy != val { println!("WY set to {:02X}", val); } self.windowy = val; } 0xFF4B => { if self.windowx != val { println!("WX set to {:02X}", val); } self.windowx = val; } 0xFF4F => self.vram_bank = val, _ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr), } } pub fn read_byte(&self, addr: u16) -> u8 { match addr { 0x8000..=0x9FFF => { if (self.vram_bank & 1) == 0 { self.vram0[(addr - 0x8000) as usize] } else { self.vram1[(addr - 0x8000) as usize] } } 0xFE00..=0xFE9F => self.oam[(addr - 0xFE00) as usize], 0xFF40 => self.control, 0xFF41 => self.status, 0xFF42 => self.scrolly, 0xFF43 => self.scrollx, 0xFF44 => self.curline, 0xFF45 => self.lyc, 0xFF47 => self.background_palette, 0xFF48 => self.object_palette[0], 0xFF49 => self.object_palette[1], 0xFF4A => self.windowy, 0xFF4B => self.windowx, 0xFF4F => self.vram_bank | 0b1111_1110, _ => panic!("Display: Read from {:04X} unsupported", addr), } } fn get_background_attribute( &self, //tile_index_x: usize, //tile_index_y: usize, vram_offset: usize, ) -> BgMapAttributes { /* let background_map = if self.control & CTRL_BG_TILE_MAP_SELECT != 0 { 0x1C00 } else { 0x1800 }; let vram_offset: usize = background_map + ((tile_index_y as usize) * 32) + tile_index_x as usize; */ // Background attributes are only in this memory range. assert!( vram_offset >= 0x1800 && vram_offset < 0x2000, format!("offset: {:04X}", vram_offset) ); BgMapAttributes(self.vram1[vram_offset]) } pub fn tick(&mut self, ticks: u16) { self.status &= 0xFC; if self.control & CTRL_LCD_DISPLAY_ENABLE == 0 { // Display is disabled self.current_ticks = 0; self.current_mode = DisplayMode::VBlank; self.curline = 0; return; } self.current_ticks += ticks; match self.current_mode { DisplayMode::ReadOAMMemory => { // Mode 2, Reading OAM memory, RAM may be accessed. if self.current_ticks > TICKS_END_SCANLINE { self.current_ticks = 0; self.current_mode = DisplayMode::ReadFullMemory; } self.status |= 2; } DisplayMode::ReadFullMemory => { // Mode 3, reading OAM, VMEM and palette data. // Nothing may be accessed. if self.current_ticks > TICKS_END_READMODE { self.current_ticks = 0; self.current_mode = DisplayMode::HBlank; if self.frameskip < self.frame_no { self.renderscan(); } if self.status & STAT_MODE_HBLANK_INT > 0 { self.stat_interrupt = true; } } self.status |= 3; } DisplayMode::HBlank => { // Mode 0, H-Blank, Memory (RAM, OAM) may be accessed. if self.current_ticks > TICKS_END_HBLANK { self.current_ticks = 0; self.curline += 1; // render scan? if self.curline == 143 { self.current_mode = DisplayMode::VBlank; // To Mode 1 if self.status & STAT_MODE_VBLANK_INT > 0 { self.stat_interrupt = true; } } else { self.current_mode = DisplayMode::ReadOAMMemory; // Mode 2 again if self.status & STAT_MODE_OAM_INT > 0 { self.stat_interrupt = true; } } } self.status &= 0xFC; self.status |= 0; } DisplayMode::VBlank => { // Mode 1, V-Blank (or display disabled), Memory (RAM, OAM) // may be accessed if self.current_ticks > TICKS_END_VBLANK { self.current_ticks = 0; self.curline += 1; if self.curline == 144 { self.vblank_interrupt = true; } if self.curline > 153 { self.current_mode = DisplayMode::ReadOAMMemory; // Mode 2, scanline. if self.frameskip < self.frame_no { self.render_screen(); self.renderer.present(); self.renderer .set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); self.renderer.clear(); self.frame_no = 0; } else { self.frame_no += 1; } self.curline = 0; if self.status & STAT_MODE_OAM_INT > 0 { self.stat_interrupt = true; } } } self.status |= 1; } } // Update the status register if self.curline == self.lyc { self.status |= 1 << 2; if self.status & STAT_LYC_LC_COINCIDENCE_INT > 0 { self.stat_interrupt = true; } } else { self.status &= !(1 << 2); } } fn render_sprites(&mut self, sprites: &[Sprite]) { if self.control & CTRL_BG_SPRITE_ENABLE > 0 { let mut num_rendered: u8 = 0; for sprite in sprites.iter().take(39) { // Gameboy limitation if num_rendered >= 10 { break; } // Skip hidden sprites if sprite.is_hidden() { continue; } // Calculate correct coords let x: u8 = sprite.x.wrapping_sub(8); // Flip sprite, TODO: What does this do in WIDE mode? let y: u8 = sprite.y.wrapping_sub(16); let render_y = self.curline; // Is this sprite on the current line? let wide_mode = self.control & CTRL_BG_SPRITE_SIZE > 0; let actual_h = if wide_mode { 16 } else { 8 }; if sprite.is_y_flipped() { panic!("Sorry, no y flip support yet"); } if y.wrapping_add(actual_h) > render_y && y <= render_y { num_rendered += 1; let tile_offset_y: usize = render_y as usize - y as usize; let tile_base_addr: usize = sprite.tile as usize * 16; let tile_addr = tile_base_addr + tile_offset_y * 2; let vram = if sprite.vram_bank() == 0 { &*self.vram0 } else { &*self.vram1 }; let tile_line_1 = vram[tile_addr + 0]; let tile_line_2 = vram[tile_addr + 1]; for x_o in 0..8 { let b1: bool; let b2: bool; let scr_x = x.wrapping_add(x_o); let scr_y = render_y; let pixel_origin = self.get_pixel_origin(scr_x, scr_y); // Do not draw if the sprite should be drawn in the background if !sprite.is_foreground() { match pixel_origin { PixelOrigin::Background | PixelOrigin::Window => continue, _ => {} } } let x_maybe_flipped: u8 = if sprite.is_x_flipped() { x_o ^ 7 } else { x_o }; b1 = (tile_line_1 & 1 << (7 - x_o)) > 0; b2 = (tile_line_2 & 1 << (7 - x_o)) > 0; // Sprites may be transparent. if !b1 && !b2 { continue; } // GB /* let c = (b1 as u8) * 2 + b2 as u8; let lookup: [u8; 4] = match sprite.palette() { 0 => [ self.object_palette[0] & 3, (self.object_palette[0] >> 2) & 3, (self.object_palette[0] >> 4) & 3, (self.object_palette[0] >> 6) & 3, ], 1 => [ self.object_palette[1] & 3, (self.object_palette[1] >> 2) & 3, (self.object_palette[1] >> 4) & 3, (self.object_palette[1] >> 6) & 3, ], _ => unreachable!(), }; let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; // Draw stuff. We're currently only in monochrome mode self.set_pixel( x.wrapping_add(x_o), render_y, sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]), PixelOrigin::Sprite, ); */ let c = ((b1 as u8) * 2 + b2 as u8) as usize; if c == 0 { continue; } let c = self.object_palette_cgb[sprite.palette() as usize].get_color(c); self.set_pixel( x.wrapping_add(x_maybe_flipped), render_y, c, PixelOrigin::Sprite, ); } } } } } fn get_bg_window_tile_addr(&self, tile_id: u8) -> usize { if (self.control & CTRL_BG_WINDOW_TILE_DATA_SELECT) == CTRL_BG_WINDOW_TILE_DATA_SELECT { let base_addr = 0x0000; base_addr + ((tile_id as usize) << 4) } else { let base_addr = 0x0800; let tile_id = (128u8 as i8).wrapping_add(tile_id as i8) as u8; base_addr + ((tile_id as usize) << 4) } } pub fn dump_vram(&self) { std::fs::File::create("vram0.dat") .unwrap() .write_all(&self.vram0) .unwrap(); std::fs::File::create("vram1.dat") .unwrap() .write_all(&self.vram1) .unwrap(); } fn renderscan(&mut self) { // Points to the background map offset to use. let background_map: usize; let window_map: usize; // verify! if self.control & CTRL_BG_TILE_MAP_SELECT == CTRL_BG_TILE_MAP_SELECT { background_map = 0x1C00; } else { background_map = 0x1800; } if self.control & CTRL_WND_TILE_MAP_SELECT == CTRL_WND_TILE_MAP_SELECT { window_map = 0x1C00; } else { window_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; // Order sprites by priority let mut queue: Vec = Vec::new(); for i in 0..39 { queue.push(Sprite { y: self.oam[i * 4 + 0], x: self.oam[i * 4 + 1], tile: self.oam[i * 4 + 2], flags: self.oam[i * 4 + 3], }); } // This is the non-CGB priority. // Smaller x coord = higher. use std::cmp; queue.sort_by(|x, y| { if x.x > y.x { cmp::Ordering::Greater } else if x.x < y.x { cmp::Ordering::Less } else { cmp::Ordering::Equal } }); queue.reverse(); // Render background if (self.control & CTRL_BG_DISPLAY) == CTRL_BG_DISPLAY { // Render pixels (20 tiles) let render_abs_y = map_offset_y.wrapping_add(render_y); let tile_index_y: u8 = render_abs_y >> 3; let tile_offset_y: u8 = render_abs_y & 7; for render_x in 0..160 { // Absolute render coordinates let render_abs_x = map_offset_x.wrapping_add(render_x); let tile_index_x: u8 = render_abs_x >> 3; let tile_offset_x: u8 = render_abs_x & 7; let vram_offset: usize = background_map + ((tile_index_y as usize) * 32) + tile_index_x as usize; // Obtain tile ID in this area let tile_id = self.vram0[vram_offset]; // GBC stuff // Get BG map attributes let bg_attribs = self.get_background_attribute(vram_offset); let vram = [&*self.vram0, &*self.vram1][bg_attribs.vram_bank_number()]; // TODO: Priority let tile_offset_y = if bg_attribs.vertical_flip() { 7 ^ tile_offset_y } else { tile_offset_y }; // Obtain tile information let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id); // TODO: Colored background let addr = tile_base_addr + (tile_offset_y as usize) * 2; let tile_line_1 = vram[addr]; let tile_line_2 = vram[addr + 1]; // Get the correct bit let b1 = if bg_attribs.horizontal_flip() { 7 ^ (1 << (7 - tile_offset_x)) } else { 1 << (7 - tile_offset_x) }; let b2 = if bg_attribs.horizontal_flip() { 7 ^ (1 << (7 - tile_offset_x)) } else { 1 << (7 - tile_offset_x) }; let b1: bool = (tile_line_1 & b1) != 0; let b2: bool = (tile_line_2 & b2) != 0; // Lookup the color let c = ((b1 as u8) * 2 + b2 as u8) as usize; let origin = match c { 0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. _ => PixelOrigin::Background, }; let c = self.background_palette_cgb[bg_attribs.palette_number()].get_color(c); self.set_pixel(render_x, render_y, c, origin); /* let c = (b1 as u8) * 2 + b2 as u8; let lookup: [u8; 4] = [ self.background_palette & 3, (self.background_palette >> 2) & 3, (self.background_palette >> 4) & 3, (self.background_palette >> 6) & 3, ]; let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; let origin = match c { 0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. _ => PixelOrigin::Background, }; self.set_pixel( render_x, render_y, sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]), origin, ); */ } } if (self.control & CTRL_WND_DISPLAY_ENABLE) == CTRL_WND_DISPLAY_ENABLE { // Draw 'window' over the background. // Screen coordinates of the top left corner are WX-7, WY if self.windowx < 167 && self.windowy < 144 { //let rx = self.windowx.wrapping_add(7); let rx = 7u8.wrapping_sub(self.windowx); let ry = render_y.wrapping_add(self.windowy); for r_x in 0..160u8 { let render_x = r_x.wrapping_add(rx); // Absolute render coordinates let tile_index_x: u8 = render_x >> 3; let tile_index_y: u8 = ry >> 3; let tile_offset_x: u8 = render_x & 7; let tile_offset_y: u8 = ry & 7; let vram_offset: usize = window_map + (tile_index_y as usize) * 32 + tile_index_x as usize; // Obtain tile ID in this area let tile_id = self.vram0[vram_offset]; let bg_attribs = self.get_background_attribute( //tile_index_x as usize, tile_index_y as usize vram_offset, ); // Get BG map attributes let vram = if bg_attribs.vram_bank_number() == 0 { &*self.vram0 } else { &*self.vram1 }; // TODO: Priority let tile_offset_x = if bg_attribs.vertical_flip() { 7 ^ tile_offset_x } else { tile_offset_x }; let tile_offset_y = if bg_attribs.horizontal_flip() { 7 ^ tile_offset_y } else { tile_offset_y }; // Obtain tile information let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id); let tile_addr = tile_base_addr + (tile_offset_y as usize) * 2; let tile_line_1 = vram[tile_addr]; let tile_line_2 = vram[tile_addr + 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 c = (b1 as u8) * 2 + b2 as u8; let origin = match c { 0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. _ => PixelOrigin::Window, }; let c = self.background_palette_cgb[bg_attribs.palette_number()] .get_color(c as usize); self.set_pixel(render_x, render_y, c, origin); /* let lookup: [u8; 4] = [ self.background_palette & 3, (self.background_palette >> 2) & 3, (self.background_palette >> 4) & 3, (self.background_palette >> 6) & 3, ]; let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize]; // Draw stuff. We're currently only in monochrome mode let origin = match c { 0 => PixelOrigin::Empty, // Hack so that objects will be in front of it. _ => PixelOrigin::Window, }; self.set_pixel( render_x, render_y, sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]), origin, ); */ } } } if (self.control & CTRL_BG_SPRITE_ENABLE) == CTRL_BG_SPRITE_ENABLE { self.render_sprites(&queue); } } }