#![allow(dead_code)]
use super::*;
#[derive(Debug, Clone)]
pub struct DMGConfig {
pub viewport_scroll_x: u8,
pub viewport_scroll_y: u8,
pub bg_win_use_signed_addressing: bool,
pub background_uses_high_screenblock: bool,
pub window_uses_high_screenblock: bool,
pub objects_are_tall: bool,
pub objects_enabled: bool,
pub window_enabled: bool,
pub bg_win_enabled: bool,
pub window_y: u8,
pub window_xp7: u8,
pub bg_win_palette: [u8; 4],
pub object_palette: [[u8; 4]; 2],
pub max_objects_per_scanline: usize,
}
impl Default for DMGConfig {
fn default() -> Self {
Self {
viewport_scroll_x: 0,
viewport_scroll_y: 0,
bg_win_use_signed_addressing: false,
background_uses_high_screenblock: false,
window_uses_high_screenblock: true,
objects_are_tall: false,
objects_enabled: false,
window_enabled: false,
bg_win_enabled: true,
window_y: 0,
window_xp7: 7,
bg_win_palette: [0, 1, 2, 3],
object_palette: [[0, 1, 2, 3], [0, 1, 2, 3]],
max_objects_per_scanline: 10,
}
}
}
#[derive(Debug, Clone)]
pub struct DMGTileMemory {
pub tiles: Vec<u8>,
}
impl DMGTileMemory {
pub const COMPRESSED_BYTES_PER_TILE: usize = 16;
pub const INFLATED_BYTES_PER_TILE: usize = 64;
pub const TILES_PER_BLOCK: usize = 128;
pub const BLOCK_COUNT: usize = 3;
}
impl Default for DMGTileMemory {
fn default() -> Self {
Self {
tiles: vec![0; Self::INFLATED_BYTES_PER_TILE * Self::TILES_PER_BLOCK * Self::BLOCK_COUNT],
}
}
}
impl DMGTileMemory {
pub fn get_tile(&self, slot: usize) -> Option<&[u8]> {
if slot < Self::TILES_PER_BLOCK * Self::BLOCK_COUNT {
let base = slot * Self::INFLATED_BYTES_PER_TILE;
Some(&self.tiles[base..(base + Self::INFLATED_BYTES_PER_TILE)])
} else {
None
}
}
pub fn get_tile_mut(&mut self, slot: usize) -> Option<&mut [u8]> {
if slot < Self::TILES_PER_BLOCK * Self::BLOCK_COUNT {
let base = slot * Self::INFLATED_BYTES_PER_TILE;
Some(&mut self.tiles[base..(base + Self::INFLATED_BYTES_PER_TILE)])
} else {
None
}
}
}
pub struct DMGScreenblock {
pub entries: Vec<u8>,
}
impl DMGScreenblock {
const PIXELS_PER_TILE_SIDE: u8 = 8;
const TILES_PER_ROW: usize = 32;
const ROW_COUNT: usize = 32;
pub fn get_pixel(&self, x: u8, y: u8, memory: &DMGTileMemory, use_signed: bool) -> u8 {
let tile_x = (x / Self::PIXELS_PER_TILE_SIDE) as usize;
let tile_y = (y / Self::PIXELS_PER_TILE_SIDE) as usize;
let entry_value = self.entries[tile_x + tile_y * Self::TILES_PER_ROW];
let tile_data = if use_signed {
let signed_value = entry_value as i8;
let index = ((2 * DMGTileMemory::TILES_PER_BLOCK) as isize + signed_value as isize) as usize;
memory.get_tile(index)
} else {
memory.get_tile(entry_value as usize)
}
.unwrap();
let sub_tile_x = (x % Self::PIXELS_PER_TILE_SIDE) as usize;
let sub_tile_y = (y % Self::PIXELS_PER_TILE_SIDE) as usize;
tile_data[sub_tile_x + sub_tile_y * Self::PIXELS_PER_TILE_SIDE as usize]
}
}
impl Default for DMGScreenblock {
fn default() -> Self {
Self {
entries: vec![0; Self::TILES_PER_ROW * Self::ROW_COUNT],
}
}
}
#[derive(Debug, Clone, Default)]
pub struct DMGOAMEntry {
pub xm8: u8,
pub ym16: u8,
pub tile_id: u8,
pub display_behind_bg_win_13: bool,
pub vflip: bool,
pub hflip: bool,
pub use_high_palette: bool,
}
impl DMGOAMEntry {
pub fn screen_left_top(&self) -> (u8, u8) {
(self.xm8.wrapping_sub(8), self.ym16.wrapping_sub(16))
}
pub fn get_pixel(&self, screen_x: u8, screen_y: u8, memory: &DMGTileMemory, tall: bool) -> u8 {
let (left, top) = self.screen_left_top();
let width = DMGScreenblock::PIXELS_PER_TILE_SIDE;
let height = DMGScreenblock::PIXELS_PER_TILE_SIDE * if tall { 2 } else { 1 };
let local_x = if self.hflip {
width - (screen_x.wrapping_sub(left) + 1)
} else {
screen_x.wrapping_sub(left)
};
let local_y = if self.vflip {
height - (screen_y.wrapping_sub(top) + 1)
} else {
screen_y.wrapping_sub(top)
};
debug_assert!(local_x < width, "local_x:{}", local_x);
debug_assert!(
local_y < height,
"local_y:{}, limit:{}, screen_y:{}, screen_top:{}",
local_y,
DMGScreenblock::PIXELS_PER_TILE_SIDE * if tall { 2 } else { 1 },
screen_y,
top
);
let index = (if tall {
if local_y < DMGScreenblock::PIXELS_PER_TILE_SIDE {
self.tile_id & 0xFE
} else {
self.tile_id | 0x01
}
} else {
self.tile_id
}) as usize;
let tile_data = memory.get_tile(index).unwrap();
let local_y = if tall && local_y >= DMGScreenblock::PIXELS_PER_TILE_SIDE {
local_y - DMGScreenblock::PIXELS_PER_TILE_SIDE
} else {
local_y
};
tile_data[(local_x + local_y * DMGScreenblock::PIXELS_PER_TILE_SIDE) as usize]
}
}
#[allow(clippy::identity_op)]
pub const fn dmg_inflate_row(low: u8, high: u8) -> [u8; 8] {
let mut out = [0; 8];
out[7] = (((1 << 0) & low) > 0) as u8 | (((((1 << 0) & high) > 0) as u8) << 1);
out[6] = (((1 << 1) & low) > 0) as u8 | (((((1 << 1) & high) > 0) as u8) << 1);
out[5] = (((1 << 2) & low) > 0) as u8 | (((((1 << 2) & high) > 0) as u8) << 1);
out[4] = (((1 << 3) & low) > 0) as u8 | (((((1 << 3) & high) > 0) as u8) << 1);
out[3] = (((1 << 4) & low) > 0) as u8 | (((((1 << 4) & high) > 0) as u8) << 1);
out[2] = (((1 << 5) & low) > 0) as u8 | (((((1 << 5) & high) > 0) as u8) << 1);
out[1] = (((1 << 6) & low) > 0) as u8 | (((((1 << 6) & high) > 0) as u8) << 1);
out[0] = (((1 << 7) & low) > 0) as u8 | (((((1 << 7) & high) > 0) as u8) << 1);
out
}
pub fn dmg_inflate_tile_data(dest: &mut [u8], src: &[u8]) {
for (dest_row, source_row) in dest.chunks_exact_mut(8).zip(src.chunks_exact(2)) {
unsafe {
dest_row.copy_from_slice(&dmg_inflate_row(
*source_row.get_unchecked(0),
*source_row.get_unchecked(1),
));
}
}
}
#[allow(clippy::cast_ptr_alignment)]
pub fn render_tile_memory(bitmap: &mut Bitmap, tile_memory: &DMGTileMemory) {
for tile_id in 0..256 {
let slice = tile_memory
.get_tile(tile_id)
.expect("why don't we have at least 256 tiles?");
let tiles_per_row = 16;
let tile_base_x = (tile_id % tiles_per_row) as isize;
let tile_base_y = (tile_id / tiles_per_row) as isize;
let bytes_per_pixel = 4;
let pixels_per_tile = 8;
let tile_base_ptr = unsafe {
bitmap
.memory
.offset(pixels_per_tile * bytes_per_pixel * tile_base_x)
.offset(pixels_per_tile * bitmap.pitch * tile_base_y)
};
for (row_dy, slice_row) in slice.chunks_exact(8).enumerate() {
let mut row_ptr = unsafe { tile_base_ptr.offset(row_dy as isize * bitmap.pitch) as *mut u32 };
for (_row_dx, byte) in slice_row.iter().enumerate() {
let color: u32 = match byte {
0 => 0xFF_ffffb5,
1 => 0xFF_7bc67b,
2 => 0xFF_6b8c42,
3 => 0xFF_5a3921,
z => unimplemented!("unexpected byte index value! {}", z),
};
unsafe {
row_ptr.write_unaligned(color);
row_ptr = row_ptr.offset(1)
};
}
}
}
}
#[allow(clippy::cast_ptr_alignment)]
pub fn render_screen_block(
bitmap: &mut Bitmap, tile_memory: &DMGTileMemory, screenblock: &DMGScreenblock, scroll_x: u8,
scroll_y: u8,
) {
const TILES_PER_ROW: usize = 32;
const PIXELS_PER_TILE: usize = 8;
assert!(bitmap.width as usize >= PIXELS_PER_TILE * TILES_PER_ROW);
assert!(bitmap.height as usize >= PIXELS_PER_TILE * TILES_PER_ROW);
let mut row_ptr = bitmap.memory;
for screen_y in 0..(PIXELS_PER_TILE * TILES_PER_ROW) {
let mut pixel_ptr = row_ptr as *mut u32;
for screen_x in 0..(PIXELS_PER_TILE * TILES_PER_ROW) {
let screenblock_pixel_x: u8 = (screen_x as u8).wrapping_add(scroll_x);
let screenblock_pixel_y: u8 = (screen_y as u8).wrapping_add(scroll_y);
let screenblock_grid_x: usize = screenblock_pixel_x as usize / PIXELS_PER_TILE;
let screenblock_grid_y: usize = screenblock_pixel_y as usize / PIXELS_PER_TILE;
let screenblock_index: usize = screenblock_grid_x + screenblock_grid_y * TILES_PER_ROW;
let tile_memory_index: u8 = *screenblock.entries.get(screenblock_index).unwrap();
let tile_data: &[u8] = tile_memory.get_tile(tile_memory_index as usize).unwrap();
let tile_pixel_x: usize = screenblock_pixel_x as usize % PIXELS_PER_TILE;
let tile_pixel_y: usize = screenblock_pixel_y as usize % PIXELS_PER_TILE;
let pal_index: u8 = *tile_data
.get(tile_pixel_y * PIXELS_PER_TILE + tile_pixel_x)
.unwrap();
let color: u32 = if screen_x as isize == 160 {
0xFF_0000FF
} else if screen_y as isize == 144 {
0xFF_00FF00
} else {
match pal_index {
0 => 0xFF_ffffb5,
1 => 0xFF_7bc67b,
2 => 0xFF_6b8c42,
3 => 0xFF_5a3921,
z => unimplemented!("unexpected byte index value! {}", z),
}
};
unsafe {
pixel_ptr.write_unaligned(color);
pixel_ptr = pixel_ptr.offset(1);
}
}
row_ptr = unsafe { row_ptr.offset(bitmap.pitch) };
}
}
#[allow(clippy::cast_ptr_alignment)]
pub fn dmg_basic_render(
bitmap: &mut Bitmap, config: &DMGConfig, tile_memory: &DMGTileMemory,
screenblocks: &[&DMGScreenblock], oam: &[DMGOAMEntry], out_palette: &[ARGB32; 4],
) {
const TILES_PER_ROW: usize = 32;
const PIXELS_PER_TILE: usize = 8;
assert!(bitmap.width as usize >= PIXELS_PER_TILE * TILES_PER_ROW);
assert!(bitmap.height as usize >= PIXELS_PER_TILE * TILES_PER_ROW);
let mut row_ptr = bitmap.memory;
for screen_y in 0..(PIXELS_PER_TILE * TILES_PER_ROW) {
let mut oam_this_row: Vec<&DMGOAMEntry> = if config.objects_enabled {
oam
.iter()
.filter(|entry| {
let (_x, y) = entry.screen_left_top();
let top = y as usize;
let bottom = top + if config.objects_are_tall { 16 } else { 8 };
screen_y >= top as usize && screen_y < bottom
})
.take(config.max_objects_per_scanline)
.collect()
} else {
vec![]
};
oam_this_row.sort_by_key(|entry| entry.xm8);
let mut pixel_ptr = row_ptr as *mut u32;
for screen_x in 0..(PIXELS_PER_TILE * TILES_PER_ROW) {
let bg_win_pal_index: Option<u8> = if config.bg_win_enabled {
Some(
if config.window_enabled
&& screen_y >= config.window_y as usize
&& screen_x >= config.window_xp7.wrapping_sub(7) as usize
{
let win_x = screen_x as u8 - config.window_xp7.wrapping_sub(7);
let win_y = screen_y as u8 - config.window_y;
screenblocks[config.window_uses_high_screenblock as usize].get_pixel(
win_x,
win_y,
tile_memory,
config.bg_win_use_signed_addressing,
)
} else {
let bg_x: u8 = (screen_x as u8).wrapping_add(config.viewport_scroll_x);
let bg_y: u8 = (screen_y as u8).wrapping_add(config.viewport_scroll_y);
screenblocks[config.background_uses_high_screenblock as usize].get_pixel(
bg_x,
bg_y,
tile_memory,
config.bg_win_use_signed_addressing,
)
},
)
} else {
None
};
let obj_index: Option<(u8, bool, bool)> = if config.objects_enabled {
oam_this_row
.iter()
.find(|entry| {
let (left, _top) = entry.screen_left_top();
screen_x >= left as usize && screen_x < (left + 8) as usize
})
.map(|entry| {
(
entry.get_pixel(
screen_x as u8,
screen_y as u8,
tile_memory,
config.objects_are_tall,
),
entry.display_behind_bg_win_13,
entry.use_high_palette,
)
})
} else {
None
};
let final_index = match (bg_win_pal_index, obj_index) {
(None, None) => 0,
(Some(bg_i), None) => config.bg_win_palette[bg_i as usize],
(None, Some((obj_i, _behind, high))) => {
config.object_palette[high as usize][obj_i as usize]
}
(Some(bg_i), Some((obj_i, behind, high))) => {
if obj_i == 0 || behind && bg_i > 0 {
config.bg_win_palette[bg_i as usize]
} else {
config.object_palette[high as usize][obj_i as usize]
}
}
};
let color: u32 = if screen_x as isize == 160 || screen_x as isize == 240 {
0xFF_0000FF
} else if screen_y as isize == 144 || screen_y as isize == 160 {
0xFF_00FF00
} else {
out_palette[final_index as usize].into()
};
unsafe {
pixel_ptr.write_unaligned(color);
pixel_ptr = pixel_ptr.offset(1);
}
}
row_ptr = unsafe { row_ptr.offset(bitmap.pitch) };
}
}
#[allow(clippy::cast_ptr_alignment)]
pub fn dmg_hblank_render<
H: FnMut(&mut DMGConfig, &mut DMGTileMemory, &mut [&mut DMGScreenblock], &mut [DMGOAMEntry]),
>(
bitmap: &mut Bitmap, config: &mut DMGConfig, tile_memory: &mut DMGTileMemory,
screenblocks: &mut [&mut DMGScreenblock], oam: &mut [DMGOAMEntry], out_palette: &[ARGB32; 4],
mut hblank: H,
) {
const TILES_PER_ROW: usize = DMGScreenblock::TILES_PER_ROW as usize;
const PIXELS_PER_TILE_SIDE: usize = DMGScreenblock::PIXELS_PER_TILE_SIDE as usize;
assert!(bitmap.width as usize >= PIXELS_PER_TILE_SIDE * TILES_PER_ROW);
assert!(bitmap.height as usize >= PIXELS_PER_TILE_SIDE * TILES_PER_ROW);
let mut row_ptr = bitmap.memory;
for screen_y in 0..(PIXELS_PER_TILE_SIDE * TILES_PER_ROW) {
let mut oam_this_row: Vec<&DMGOAMEntry> = if config.objects_enabled {
oam
.iter()
.filter(|entry| {
let (_x, y) = entry.screen_left_top();
for dy in 0..if config.objects_are_tall { 16 } else { 8 } {
let augmented_y = y.wrapping_add(dy) as usize;
if augmented_y == screen_y {
return true;
}
}
false
})
.take(config.max_objects_per_scanline)
.collect()
} else {
vec![]
};
oam_this_row.sort_by_key(|entry| entry.xm8);
let mut pixel_ptr = row_ptr as *mut u32;
for screen_x in 0..(PIXELS_PER_TILE_SIDE * TILES_PER_ROW) {
let bg_win_pal_index: Option<u8> = if config.bg_win_enabled {
Some(
if config.window_enabled
&& screen_y >= config.window_y as usize
&& screen_x >= config.window_xp7.wrapping_sub(7) as usize
{
let win_x = screen_x as u8 - config.window_xp7.wrapping_sub(7);
let win_y = screen_y as u8 - config.window_y;
screenblocks[config.window_uses_high_screenblock as usize].get_pixel(
win_x,
win_y,
tile_memory,
config.bg_win_use_signed_addressing,
)
} else {
let bg_x: u8 = (screen_x as u8).wrapping_add(config.viewport_scroll_x);
let bg_y: u8 = (screen_y as u8).wrapping_add(config.viewport_scroll_y);
screenblocks[config.background_uses_high_screenblock as usize].get_pixel(
bg_x,
bg_y,
tile_memory,
config.bg_win_use_signed_addressing,
)
},
)
} else {
None
};
let obj_index: Option<(u8, bool, bool)> = if config.objects_enabled {
oam_this_row
.iter()
.find(|entry| {
let (left, _top) = entry.screen_left_top();
for dx in 0..8 {
let augmented_x = left.wrapping_add(dx) as usize;
if augmented_x == screen_x {
return true;
}
}
false
})
.map(|entry| {
(
entry.get_pixel(
screen_x as u8,
screen_y as u8,
tile_memory,
config.objects_are_tall,
),
entry.display_behind_bg_win_13,
entry.use_high_palette,
)
})
} else {
None
};
let final_index = match (bg_win_pal_index, obj_index) {
(None, None) => 0,
(Some(bg_i), None) => config.bg_win_palette[bg_i as usize],
(None, Some((obj_i, _behind, high))) => {
config.object_palette[high as usize][obj_i as usize]
}
(Some(bg_i), Some((obj_i, behind, high))) => {
if obj_i == 0 || behind && bg_i > 0 {
config.bg_win_palette[bg_i as usize]
} else {
config.object_palette[high as usize][obj_i as usize]
}
}
};
let color: u32 = if screen_x as isize == 160 || screen_x as isize == 240 {
0xFF_0000FF
} else if screen_y as isize == 144 || screen_y as isize == 160 {
0xFF_00FF00
} else {
out_palette[final_index as usize].into()
};
unsafe {
pixel_ptr.write_unaligned(color);
pixel_ptr = pixel_ptr.offset(1);
}
}
row_ptr = unsafe { row_ptr.offset(bitmap.pitch) };
hblank(config, tile_memory, screenblocks, oam);
}
}