Playing around with reading gameboy roms, and maybe emulation

wip

Changed files
+126 -49
src
+1 -2
src/main.rs
···
)
.unwrap();
let mut tile_ids: Vec<u8> = (0..100).collect();
-
let tile_map_buffer = gpu.render_tile_map(&tile_ids, 166, 144);
-
// let idk = gpu.render_tile_to_rgb(1).unwrap();
+
let tile_map_buffer = gpu.render_background_to_rgb(true, true, 25, 25); // let idk = gpu.render_tile_to_rgb(1).unwrap();
let buffer_u32: Vec<u32> = tile_map_buffer
.iter()
.map(|(r, g, b)| ((*r as u32) << 16) | ((*g as u32) << 8) | (*b as u32))
+125 -47
src/tile_map.rs
···
pub const VRAM_END: usize = 0x9FFF;
pub const VRAM_SIZE: usize = VRAM_END - VRAM_BEGIN + 1;
+
// Tilemap locations in VRAM
+
pub const TILEMAP_0_START: usize = 0x1800; // $9800 - $8000 = 0x1800
+
pub const TILEMAP_1_START: usize = 0x1C00; // $9C00 - $8000 = 0x1C00
+
pub const TILEMAP_SIZE: usize = 32 * 32; // 1024 bytes
+
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TilePixelValue {
Zero,
···
}
}
-
/// Get a tile by its index
-
pub fn get_tile(&self, tile_index: usize) -> Option<&Tile> {
-
if tile_index < self.tile_set.len() {
-
Some(&self.tile_set[tile_index])
+
/// Get a tile by its index, handling Game Boy's two addressing modes
+
pub fn get_tile(&self, tile_index: u8, use_signed_addressing: bool) -> Option<&Tile> {
+
let actual_index = if use_signed_addressing {
+
// Signed addressing mode: $8800-$97FF
+
// Index 0-127 maps to tiles 256-383, index 128-255 maps to tiles 0-127
+
if tile_index < 128 {
+
256 + tile_index as usize
+
} else {
+
(tile_index as i8 as i16 + 256) as usize
+
}
+
} else {
+
// Unsigned addressing mode: $8000-$8FFF
+
tile_index as usize
+
};
+
+
if actual_index < self.tile_set.len() {
+
Some(&self.tile_set[actual_index])
} else {
None
}
}
-
/// Render a tile to a color buffer (64 pixels as RGB values)
-
pub fn render_tile_to_rgb(&self, tile_index: usize) -> Option<[(u8, u8, u8); 64]> {
-
let tile = self.get_tile(tile_index)?;
-
let mut color_buffer = [(0, 0, 0); 64];
+
/// Read tilemap data from VRAM
+
pub fn get_tilemap_data(&self, tilemap_select: bool) -> [u8; TILEMAP_SIZE] {
+
let start_addr = if tilemap_select {
+
TILEMAP_1_START
+
} else {
+
TILEMAP_0_START
+
};
-
for (row_idx, row) in tile.iter().enumerate() {
-
for (col_idx, &pixel) in row.iter().enumerate() {
-
let buffer_index = row_idx * 8 + col_idx;
-
color_buffer[buffer_index] = pixel.to_gameboy_green();
-
}
+
let mut tilemap = [0u8; TILEMAP_SIZE];
+
for i in 0..TILEMAP_SIZE {
+
tilemap[i] = self.vram[start_addr + i];
}
-
-
Some(color_buffer)
+
tilemap
}
-
/// Render a tile to grayscale buffer (64 pixels as grayscale values)
-
pub fn render_tile_to_grayscale(&self, tile_index: usize) -> Option<[u8; 64]> {
-
let tile = self.get_tile(tile_index)?;
-
let mut gray_buffer = [0u8; 64];
+
/// Render the entire tilemap to RGB (256x256 pixels)
+
pub fn render_full_tilemap_to_rgb(
+
&self,
+
tilemap_select: bool,
+
use_signed_addressing: bool,
+
) -> Vec<(u8, u8, u8)> {
+
let tilemap_data = self.get_tilemap_data(tilemap_select);
+
let total_pixels = 256 * 256; // 32x32 tiles, each 8x8 pixels
+
let mut color_buffer = vec![(0, 0, 0); total_pixels];
-
for (row_idx, row) in tile.iter().enumerate() {
-
for (col_idx, &pixel) in row.iter().enumerate() {
-
let buffer_index = row_idx * 8 + col_idx;
-
gray_buffer[buffer_index] = pixel.to_grayscale();
+
for tilemap_y in 0..32 {
+
for tilemap_x in 0..32 {
+
let tilemap_index = tilemap_y * 32 + tilemap_x;
+
let tile_id = tilemap_data[tilemap_index];
+
+
if let Some(tile) = self.get_tile(tile_id, use_signed_addressing) {
+
// Render this tile into the color buffer
+
for tile_row in 0..8 {
+
for tile_col in 0..8 {
+
let pixel_x = tilemap_x * 8 + tile_col;
+
let pixel_y = tilemap_y * 8 + tile_row;
+
let buffer_index = pixel_y * 256 + pixel_x;
+
+
if buffer_index < color_buffer.len() {
+
color_buffer[buffer_index] =
+
tile[tile_row][tile_col].to_gameboy_green();
+
}
+
}
+
}
+
}
}
}
-
Some(gray_buffer)
+
color_buffer
}
-
/// Render multiple tiles in a grid pattern
-
pub fn render_tile_map(
+
/// Render a visible portion of the tilemap (160x144 pixels) with scrolling
+
pub fn render_background_to_rgb(
&self,
-
tile_indices: &[u8],
-
map_width: usize,
-
map_height: usize,
+
tilemap_select: bool,
+
use_signed_addressing: bool,
+
scroll_x: u8,
+
scroll_y: u8,
) -> Vec<(u8, u8, u8)> {
-
let total_pixels = map_width * 8 * map_height * 8; // 8x8 pixels per tile
-
let mut color_buffer = vec![(0, 0, 0); total_pixels];
+
let tilemap_data = self.get_tilemap_data(tilemap_select);
+
let mut color_buffer = vec![(0, 0, 0); 160 * 144];
+
+
for screen_y in 0..144 {
+
for screen_x in 0..160 {
+
// Calculate the position in the 256x256 tilemap with wrapping
+
let bg_x = ((screen_x as u16 + scroll_x as u16) % 256) as u8;
+
let bg_y = ((screen_y as u16 + scroll_y as u16) % 256) as u8;
-
for (map_idx, &tile_idx) in tile_indices.iter().enumerate() {
-
if let Some(tile) = self.get_tile(tile_idx as usize) {
-
let tile_x = map_idx % map_width;
-
let tile_y = map_idx / map_width;
+
// Which tile are we in?
+
let tile_x = (bg_x / 8) as usize;
+
let tile_y = (bg_y / 8) as usize;
+
let tilemap_index = tile_y * 32 + tile_x;
-
for (row_idx, row) in tile.iter().enumerate() {
-
for (col_idx, &pixel) in row.iter().enumerate() {
-
let pixel_x = tile_x * 8 + col_idx;
-
let pixel_y = tile_y * 8 + row_idx;
-
let buffer_index = pixel_y * (map_width * 8) + pixel_x;
+
// Which pixel within that tile?
+
let pixel_x = (bg_x % 8) as usize;
+
let pixel_y = (bg_y % 8) as usize;
-
if buffer_index < color_buffer.len() {
-
color_buffer[buffer_index] = pixel.to_gameboy_green();
-
}
-
}
+
let tile_id = tilemap_data[tilemap_index];
+
+
if let Some(tile) = self.get_tile(tile_id, use_signed_addressing) {
+
let buffer_index = screen_y * 160 + screen_x;
+
color_buffer[buffer_index] = tile[pixel_y][pixel_x].to_gameboy_green();
}
}
}
···
color_buffer
}
+
/// Render a tile to a color buffer (64 pixels as RGB values)
+
pub fn render_tile_to_rgb(&self, tile_index: usize) -> Option<[(u8, u8, u8); 64]> {
+
if tile_index >= self.tile_set.len() {
+
return None;
+
}
+
+
let tile = &self.tile_set[tile_index];
+
let mut color_buffer = [(0, 0, 0); 64];
+
+
for (row_idx, row) in tile.iter().enumerate() {
+
for (col_idx, &pixel) in row.iter().enumerate() {
+
let buffer_index = row_idx * 8 + col_idx;
+
color_buffer[buffer_index] = pixel.to_gameboy_green();
+
}
+
}
+
+
Some(color_buffer)
+
}
+
+
/// Debug function to print tilemap as hex values
+
pub fn print_tilemap_hex(&self, tilemap_select: bool) {
+
let tilemap_data = self.get_tilemap_data(tilemap_select);
+
println!("Tilemap {} contents:", if tilemap_select { 1 } else { 0 });
+
+
for row in 0..32 {
+
for col in 0..32 {
+
let index = row * 32 + col;
+
print!("{:02X} ", tilemap_data[index]);
+
}
+
println!();
+
}
+
}
+
/// Debug function to print a tile as ASCII art
pub fn print_tile_ascii(&self, tile_index: usize) {
-
if let Some(tile) = self.get_tile(tile_index) {
-
// println!("Tile {}:", tile_index);
+
if let Some(tile) = self.tile_set.get(tile_index) {
+
println!("Tile {}:", tile_index);
for row in tile {
for &pixel in row {
let char = match pixel {
···
};
print!("{}", char);
}
-
// println!();
+
println!();
}
} else {
println!("Tile {} not found", tile_index);