Playing around with reading gameboy roms, and maybe emulation

wip

+126 -49
+1 -2
src/main.rs
··· 96 96 ) 97 97 .unwrap(); 98 98 let mut tile_ids: Vec<u8> = (0..100).collect(); 99 - let tile_map_buffer = gpu.render_tile_map(&tile_ids, 166, 144); 100 - // let idk = gpu.render_tile_to_rgb(1).unwrap(); 99 + let tile_map_buffer = gpu.render_background_to_rgb(true, true, 25, 25); // let idk = gpu.render_tile_to_rgb(1).unwrap(); 101 100 let buffer_u32: Vec<u32> = tile_map_buffer 102 101 .iter() 103 102 .map(|(r, g, b)| ((*r as u32) << 16) | ((*g as u32) << 8) | (*b as u32))
+125 -47
src/tile_map.rs
··· 2 2 pub const VRAM_END: usize = 0x9FFF; 3 3 pub const VRAM_SIZE: usize = VRAM_END - VRAM_BEGIN + 1; 4 4 5 + // Tilemap locations in VRAM 6 + pub const TILEMAP_0_START: usize = 0x1800; // $9800 - $8000 = 0x1800 7 + pub const TILEMAP_1_START: usize = 0x1C00; // $9C00 - $8000 = 0x1C00 8 + pub const TILEMAP_SIZE: usize = 32 * 32; // 1024 bytes 9 + 5 10 #[derive(Copy, Clone, Debug, PartialEq)] 6 11 pub enum TilePixelValue { 7 12 Zero, ··· 102 107 } 103 108 } 104 109 105 - /// Get a tile by its index 106 - pub fn get_tile(&self, tile_index: usize) -> Option<&Tile> { 107 - if tile_index < self.tile_set.len() { 108 - Some(&self.tile_set[tile_index]) 110 + /// Get a tile by its index, handling Game Boy's two addressing modes 111 + pub fn get_tile(&self, tile_index: u8, use_signed_addressing: bool) -> Option<&Tile> { 112 + let actual_index = if use_signed_addressing { 113 + // Signed addressing mode: $8800-$97FF 114 + // Index 0-127 maps to tiles 256-383, index 128-255 maps to tiles 0-127 115 + if tile_index < 128 { 116 + 256 + tile_index as usize 117 + } else { 118 + (tile_index as i8 as i16 + 256) as usize 119 + } 120 + } else { 121 + // Unsigned addressing mode: $8000-$8FFF 122 + tile_index as usize 123 + }; 124 + 125 + if actual_index < self.tile_set.len() { 126 + Some(&self.tile_set[actual_index]) 109 127 } else { 110 128 None 111 129 } 112 130 } 113 131 114 - /// Render a tile to a color buffer (64 pixels as RGB values) 115 - pub fn render_tile_to_rgb(&self, tile_index: usize) -> Option<[(u8, u8, u8); 64]> { 116 - let tile = self.get_tile(tile_index)?; 117 - let mut color_buffer = [(0, 0, 0); 64]; 132 + /// Read tilemap data from VRAM 133 + pub fn get_tilemap_data(&self, tilemap_select: bool) -> [u8; TILEMAP_SIZE] { 134 + let start_addr = if tilemap_select { 135 + TILEMAP_1_START 136 + } else { 137 + TILEMAP_0_START 138 + }; 118 139 119 - for (row_idx, row) in tile.iter().enumerate() { 120 - for (col_idx, &pixel) in row.iter().enumerate() { 121 - let buffer_index = row_idx * 8 + col_idx; 122 - color_buffer[buffer_index] = pixel.to_gameboy_green(); 123 - } 140 + let mut tilemap = [0u8; TILEMAP_SIZE]; 141 + for i in 0..TILEMAP_SIZE { 142 + tilemap[i] = self.vram[start_addr + i]; 124 143 } 125 - 126 - Some(color_buffer) 144 + tilemap 127 145 } 128 146 129 - /// Render a tile to grayscale buffer (64 pixels as grayscale values) 130 - pub fn render_tile_to_grayscale(&self, tile_index: usize) -> Option<[u8; 64]> { 131 - let tile = self.get_tile(tile_index)?; 132 - let mut gray_buffer = [0u8; 64]; 147 + /// Render the entire tilemap to RGB (256x256 pixels) 148 + pub fn render_full_tilemap_to_rgb( 149 + &self, 150 + tilemap_select: bool, 151 + use_signed_addressing: bool, 152 + ) -> Vec<(u8, u8, u8)> { 153 + let tilemap_data = self.get_tilemap_data(tilemap_select); 154 + let total_pixels = 256 * 256; // 32x32 tiles, each 8x8 pixels 155 + let mut color_buffer = vec![(0, 0, 0); total_pixels]; 133 156 134 - for (row_idx, row) in tile.iter().enumerate() { 135 - for (col_idx, &pixel) in row.iter().enumerate() { 136 - let buffer_index = row_idx * 8 + col_idx; 137 - gray_buffer[buffer_index] = pixel.to_grayscale(); 157 + for tilemap_y in 0..32 { 158 + for tilemap_x in 0..32 { 159 + let tilemap_index = tilemap_y * 32 + tilemap_x; 160 + let tile_id = tilemap_data[tilemap_index]; 161 + 162 + if let Some(tile) = self.get_tile(tile_id, use_signed_addressing) { 163 + // Render this tile into the color buffer 164 + for tile_row in 0..8 { 165 + for tile_col in 0..8 { 166 + let pixel_x = tilemap_x * 8 + tile_col; 167 + let pixel_y = tilemap_y * 8 + tile_row; 168 + let buffer_index = pixel_y * 256 + pixel_x; 169 + 170 + if buffer_index < color_buffer.len() { 171 + color_buffer[buffer_index] = 172 + tile[tile_row][tile_col].to_gameboy_green(); 173 + } 174 + } 175 + } 176 + } 138 177 } 139 178 } 140 179 141 - Some(gray_buffer) 180 + color_buffer 142 181 } 143 182 144 - /// Render multiple tiles in a grid pattern 145 - pub fn render_tile_map( 183 + /// Render a visible portion of the tilemap (160x144 pixels) with scrolling 184 + pub fn render_background_to_rgb( 146 185 &self, 147 - tile_indices: &[u8], 148 - map_width: usize, 149 - map_height: usize, 186 + tilemap_select: bool, 187 + use_signed_addressing: bool, 188 + scroll_x: u8, 189 + scroll_y: u8, 150 190 ) -> Vec<(u8, u8, u8)> { 151 - let total_pixels = map_width * 8 * map_height * 8; // 8x8 pixels per tile 152 - let mut color_buffer = vec![(0, 0, 0); total_pixels]; 191 + let tilemap_data = self.get_tilemap_data(tilemap_select); 192 + let mut color_buffer = vec![(0, 0, 0); 160 * 144]; 193 + 194 + for screen_y in 0..144 { 195 + for screen_x in 0..160 { 196 + // Calculate the position in the 256x256 tilemap with wrapping 197 + let bg_x = ((screen_x as u16 + scroll_x as u16) % 256) as u8; 198 + let bg_y = ((screen_y as u16 + scroll_y as u16) % 256) as u8; 153 199 154 - for (map_idx, &tile_idx) in tile_indices.iter().enumerate() { 155 - if let Some(tile) = self.get_tile(tile_idx as usize) { 156 - let tile_x = map_idx % map_width; 157 - let tile_y = map_idx / map_width; 200 + // Which tile are we in? 201 + let tile_x = (bg_x / 8) as usize; 202 + let tile_y = (bg_y / 8) as usize; 203 + let tilemap_index = tile_y * 32 + tile_x; 158 204 159 - for (row_idx, row) in tile.iter().enumerate() { 160 - for (col_idx, &pixel) in row.iter().enumerate() { 161 - let pixel_x = tile_x * 8 + col_idx; 162 - let pixel_y = tile_y * 8 + row_idx; 163 - let buffer_index = pixel_y * (map_width * 8) + pixel_x; 205 + // Which pixel within that tile? 206 + let pixel_x = (bg_x % 8) as usize; 207 + let pixel_y = (bg_y % 8) as usize; 164 208 165 - if buffer_index < color_buffer.len() { 166 - color_buffer[buffer_index] = pixel.to_gameboy_green(); 167 - } 168 - } 209 + let tile_id = tilemap_data[tilemap_index]; 210 + 211 + if let Some(tile) = self.get_tile(tile_id, use_signed_addressing) { 212 + let buffer_index = screen_y * 160 + screen_x; 213 + color_buffer[buffer_index] = tile[pixel_y][pixel_x].to_gameboy_green(); 169 214 } 170 215 } 171 216 } ··· 173 218 color_buffer 174 219 } 175 220 221 + /// Render a tile to a color buffer (64 pixels as RGB values) 222 + pub fn render_tile_to_rgb(&self, tile_index: usize) -> Option<[(u8, u8, u8); 64]> { 223 + if tile_index >= self.tile_set.len() { 224 + return None; 225 + } 226 + 227 + let tile = &self.tile_set[tile_index]; 228 + let mut color_buffer = [(0, 0, 0); 64]; 229 + 230 + for (row_idx, row) in tile.iter().enumerate() { 231 + for (col_idx, &pixel) in row.iter().enumerate() { 232 + let buffer_index = row_idx * 8 + col_idx; 233 + color_buffer[buffer_index] = pixel.to_gameboy_green(); 234 + } 235 + } 236 + 237 + Some(color_buffer) 238 + } 239 + 240 + /// Debug function to print tilemap as hex values 241 + pub fn print_tilemap_hex(&self, tilemap_select: bool) { 242 + let tilemap_data = self.get_tilemap_data(tilemap_select); 243 + println!("Tilemap {} contents:", if tilemap_select { 1 } else { 0 }); 244 + 245 + for row in 0..32 { 246 + for col in 0..32 { 247 + let index = row * 32 + col; 248 + print!("{:02X} ", tilemap_data[index]); 249 + } 250 + println!(); 251 + } 252 + } 253 + 176 254 /// Debug function to print a tile as ASCII art 177 255 pub fn print_tile_ascii(&self, tile_index: usize) { 178 - if let Some(tile) = self.get_tile(tile_index) { 179 - // println!("Tile {}:", tile_index); 256 + if let Some(tile) = self.tile_set.get(tile_index) { 257 + println!("Tile {}:", tile_index); 180 258 for row in tile { 181 259 for &pixel in row { 182 260 let char = match pixel { ··· 187 265 }; 188 266 print!("{}", char); 189 267 } 190 - // println!(); 268 + println!(); 191 269 } 192 270 } else { 193 271 println!("Tile {} not found", tile_index);