gb-emu/src/processor/gpu.rs

469 lines
14 KiB
Rust
Raw Normal View History

use self::tile_window::TileWindow;
2023-02-07 09:12:39 +11:00
use crate::{
processor::{as_signed, get_bit, set_bit, set_or_clear_bit, CPU},
FACTOR, HEIGHT, WIDTH,
};
2023-02-06 12:14:51 +11:00
use minifb::{Window, WindowOptions};
mod tile_window;
2023-02-05 18:46:55 +11:00
#[derive(PartialEq)]
enum DrawMode {
HBlank,
VBlank,
Mode2,
Mode3,
}
2023-02-06 19:17:52 +11:00
#[derive(Debug)]
2023-02-05 22:37:49 +11:00
enum TilemapArea {
T9800,
T9C00,
}
2023-02-06 11:42:45 +11:00
impl TilemapArea {
2023-02-08 10:09:24 +11:00
fn get_addr(&self, addr: u16) -> u16 {
2023-02-06 11:42:45 +11:00
match self {
2023-02-08 10:09:24 +11:00
TilemapArea::T9800 => 0x9800 + addr,
TilemapArea::T9C00 => 0x9C00 + addr,
2023-02-06 11:42:45 +11:00
}
}
}
2023-02-06 19:17:52 +11:00
#[derive(Debug)]
2023-02-05 22:37:49 +11:00
enum TiledataArea {
D8000,
2023-02-06 11:42:45 +11:00
D9000,
2023-02-05 22:37:49 +11:00
}
2023-02-05 23:07:55 +11:00
impl TiledataArea {
fn get_addr(&self, addr: u8) -> u16 {
match self {
2023-02-06 12:14:51 +11:00
TiledataArea::D8000 => 0x8000 + ((addr as u16) * 16),
TiledataArea::D9000 => 0x9000_u16.wrapping_add_signed((as_signed(addr) as i16) * 16),
2023-02-05 23:07:55 +11:00
}
}
}
2023-02-08 10:32:06 +11:00
#[derive(Debug, PartialEq)]
2023-02-05 22:37:49 +11:00
enum ObjSize {
S8x8,
S8x16,
}
2023-02-06 19:17:52 +11:00
impl ObjSize {
fn get_height(&self) -> u8 {
match self {
ObjSize::S8x8 => 8,
ObjSize::S8x16 => 16,
}
}
}
#[derive(Debug)]
2023-02-05 22:37:49 +11:00
struct LCDC {
enable: bool,
window_tilemap: TilemapArea,
window_enable: bool,
tile_area: TiledataArea,
bg_tilemap: TilemapArea,
obj_size: ObjSize,
obj_enable: bool,
bg_window_enable: bool,
}
2023-02-06 11:42:45 +11:00
#[derive(Clone, Copy, Debug)]
2023-02-05 22:37:49 +11:00
enum Colour {
White,
LightGray,
DarkGray,
Black,
}
impl Colour {
fn to_rgb(&self) -> u32 {
match self {
2023-02-08 10:30:21 +11:00
Colour::White => Self::from_u8_rgb(0xFF, 0xFF, 0xFF),
Colour::LightGray => Self::from_u8_rgb(0xAA, 0xAA, 0xAA),
Colour::DarkGray => Self::from_u8_rgb(0x55, 0x55, 0x55),
Colour::Black => Self::from_u8_rgb(0x00, 0x00, 0x00),
2023-02-05 22:37:49 +11:00
}
}
fn from_u8_rgb(r: u8, g: u8, b: u8) -> u32 {
let (r, g, b) = (r as u32, g as u32, b as u32);
(r << 16) | (g << 8) | b
}
}
#[derive(Clone, Copy)]
struct Palette {
zero: Colour,
one: Colour,
two: Colour,
three: Colour,
}
2023-02-06 19:17:52 +11:00
struct ObjectFlags {
behind_bg_and_window: bool,
y_flip: bool,
x_flip: bool,
palette: Palette,
}
struct Object {
x: u8,
y: u8,
tile_index: u8,
flags: ObjectFlags,
}
const TILE_WINDOW_WIDTH: usize = 16 * 8;
const TILE_WINDOW_HEIGHT: usize = 24 * 8;
const TILE_WINDOW_WIDTH_SCALED: usize = TILE_WINDOW_WIDTH * FACTOR;
const TILE_WINDOW_HEIGHT_SCALED: usize = TILE_WINDOW_HEIGHT * FACTOR;
2023-02-06 12:14:51 +11:00
2023-02-05 18:46:55 +11:00
pub struct GPU {
pub buffer: Vec<u32>,
2023-02-05 22:56:18 +11:00
scaled_buffer: Vec<u32>,
2023-02-05 18:46:55 +11:00
mode: DrawMode,
mode_clock: usize,
2023-02-05 22:37:49 +11:00
scanline: u8,
tile_window: Option<TileWindow>,
2023-02-05 18:46:55 +11:00
}
impl GPU {
pub(super) fn new(enable_tile_window: bool) -> Self {
let tile_window = if enable_tile_window {
let mut window = Window::new(
"Tiles",
TILE_WINDOW_WIDTH_SCALED,
TILE_WINDOW_HEIGHT_SCALED,
WindowOptions::default(),
)
.unwrap_or_else(|e| {
panic!("{}", e);
});
window.set_position((550 + (WIDTH * FACTOR)) as isize, 50);
window.topmost(true);
Some(TileWindow::new(window))
} else {
None
};
2023-02-05 18:46:55 +11:00
Self {
buffer: vec![0; WIDTH * HEIGHT],
2023-02-05 22:56:18 +11:00
scaled_buffer: vec![0; WIDTH * HEIGHT * 4],
2023-02-05 18:46:55 +11:00
mode: DrawMode::Mode2,
mode_clock: 0,
scanline: 0,
tile_window,
2023-02-05 18:46:55 +11:00
}
}
}
impl CPU {
pub fn advance_gpu_clock(&mut self, steps: u8) {
let real_steps = (steps as usize) * 4;
self.gpu.mode_clock += real_steps;
2023-02-05 22:37:49 +11:00
let lcdc = self.get_lcdc();
2023-02-05 18:46:55 +11:00
match self.gpu.mode {
DrawMode::HBlank => {
// mode 0: hblank
if self.gpu.mode_clock >= 204 {
self.gpu.mode_clock = 0;
self.gpu.scanline += 1;
if self.gpu.scanline == 143 {
2023-02-05 22:37:49 +11:00
self.enter_vblank(&lcdc);
2023-02-05 18:46:55 +11:00
} else {
self.gpu.mode = DrawMode::Mode2;
}
}
}
DrawMode::VBlank => {
// mode 1: vblank
if self.gpu.mode_clock >= 456 {
self.gpu.mode_clock = 0;
self.gpu.scanline += 1;
if self.gpu.scanline == 153 {
2023-02-05 18:50:07 +11:00
self.exit_vblank();
2023-02-05 18:46:55 +11:00
}
}
}
DrawMode::Mode2 => {
// search oam for sprites on this line
// we dont really have to emulate this
if self.gpu.mode_clock >= 80 {
self.gpu.mode_clock = 0;
self.gpu.mode = DrawMode::Mode3;
}
}
DrawMode::Mode3 => {
// generate scanline
if self.gpu.mode_clock >= 172 {
self.gpu.mode_clock = 0;
2023-02-05 22:37:49 +11:00
self.enter_hblank(&lcdc);
2023-02-05 18:46:55 +11:00
}
}
}
self.set_lcd_status();
}
2023-02-05 22:37:49 +11:00
fn get_lcdc(&self) -> LCDC {
let reg = self.memory.get(0xFF40);
LCDC {
enable: get_bit(reg, 7),
window_tilemap: if get_bit(reg, 6) {
TilemapArea::T9C00
} else {
TilemapArea::T9800
},
window_enable: get_bit(reg, 5),
tile_area: if get_bit(reg, 4) {
TiledataArea::D8000
} else {
2023-02-06 11:42:45 +11:00
TiledataArea::D9000
2023-02-05 22:37:49 +11:00
},
bg_tilemap: if get_bit(reg, 3) {
TilemapArea::T9C00
} else {
TilemapArea::T9800
},
obj_size: if get_bit(reg, 2) {
ObjSize::S8x16
} else {
ObjSize::S8x8
},
obj_enable: get_bit(reg, 1),
bg_window_enable: get_bit(reg, 0),
}
}
fn enter_hblank(&mut self, lcdc: &LCDC) {
2023-02-05 18:46:55 +11:00
self.gpu.mode = DrawMode::HBlank;
2023-02-05 22:37:49 +11:00
self.render_scanline(self.gpu.scanline, lcdc);
2023-02-05 18:46:55 +11:00
}
2023-02-05 22:37:49 +11:00
fn enter_vblank(&mut self, lcdc: &LCDC) {
2023-02-07 09:09:52 +11:00
self.memory.update_pressed_keys(self.window.get_keys());
2023-02-05 18:46:55 +11:00
self.gpu.mode = DrawMode::VBlank;
2023-02-05 22:56:18 +11:00
if lcdc.enable {
2023-02-05 23:07:55 +11:00
self.render_window();
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0));
2023-02-05 22:56:18 +11:00
}
2023-02-05 18:46:55 +11:00
}
2023-02-05 18:50:07 +11:00
fn exit_vblank(&mut self) {
self.gpu.mode = DrawMode::Mode2;
self.gpu.scanline = 0;
if let Some(tile_window) = &mut self.gpu.tile_window {
tile_window.draw_sprite_window(byte_to_palette(self.memory.get(0xFF47)), &self.memory);
}
2023-02-05 18:50:07 +11:00
}
2023-02-05 18:46:55 +11:00
fn set_lcd_status(&mut self) {
let mut stat = self.memory.get(0xFF41);
stat = set_or_clear_bit(stat, 2, self.memory.get(0xFF44) == self.memory.get(0xFF45));
stat = set_or_clear_bit(
stat,
1,
(self.gpu.mode == DrawMode::Mode2) || (self.gpu.mode == DrawMode::Mode3),
);
stat = set_or_clear_bit(
stat,
0,
(self.gpu.mode == DrawMode::VBlank) || (self.gpu.mode == DrawMode::Mode3),
);
self.memory.set(0xFF41, stat);
2023-02-05 22:37:49 +11:00
self.memory.set(0xFF44, self.gpu.scanline);
}
fn render_scanline(&mut self, scanline: u8, lcdc: &LCDC) {
if lcdc.bg_window_enable {
let palette = byte_to_palette(self.memory.get(0xFF47));
self.render_scanline_bg(scanline, lcdc, palette);
2023-02-05 22:37:49 +11:00
if lcdc.window_enable {
self.render_scanline_window(scanline, lcdc, palette);
2023-02-05 22:37:49 +11:00
}
2023-02-08 09:20:57 +11:00
} else {
for x in 0..WIDTH {
self.gpu.buffer[(scanline as usize * WIDTH) + x] =
Colour::from_u8_rgb(255, 255, 255);
}
2023-02-05 22:37:49 +11:00
}
if lcdc.obj_enable {
self.render_scanline_obj(scanline, lcdc);
}
2023-02-05 18:46:55 +11:00
}
fn render_scanline_bg(&mut self, scanline: u8, lcdc: &LCDC, palette: Palette) {
2023-02-05 22:37:49 +11:00
let scroll_y = self.memory.get(0xFF42);
let scroll_x = self.memory.get(0xFF43);
2023-02-05 23:07:55 +11:00
self.render_tiles(
scanline,
&lcdc.bg_tilemap,
&lcdc.tile_area,
palette,
scroll_x,
scroll_y,
);
2023-02-05 22:37:49 +11:00
}
fn render_scanline_window(&mut self, scanline: u8, lcdc: &LCDC, palette: Palette) {
2023-02-05 22:37:49 +11:00
let pos_y = self.memory.get(0xFF4A);
// subtracting 7 to get the Real Number...
let pos_x = self.memory.get(0xFF4B).wrapping_sub(7);
if pos_y < 143 && pos_x < 166 {
self.render_tiles(
scanline,
&lcdc.window_tilemap,
&lcdc.tile_area,
palette,
pos_x,
pos_y,
)
2023-02-05 22:37:49 +11:00
}
}
2023-02-06 19:17:52 +11:00
fn render_scanline_obj(&mut self, scanline: u8, lcdc: &LCDC) {
let objs = self.parse_oam(scanline, &lcdc.obj_size);
for object in objs {
self.render_object(scanline, object, &lcdc.obj_size);
}
}
fn parse_oam(&mut self, scanline: u8, obj_size: &ObjSize) -> Vec<Object> {
let mut objs = vec![];
for i in (0xFE00..0xFE9F).step_by(4) {
let y_pos = self.memory.get(i).wrapping_sub(16);
if y_pos <= scanline && (y_pos + obj_size.get_height()) > scanline {
// sprite is on line
let x_pos = self.memory.get(i + 1);
let tile_index = self.memory.get(i + 2);
let flags = self.memory.get(i + 3);
let palette_addr = if get_bit(flags, 4) { 0xFF49 } else { 0xFF48 };
objs.push(Object {
x: x_pos,
y: y_pos,
tile_index,
flags: ObjectFlags {
behind_bg_and_window: get_bit(flags, 7),
y_flip: get_bit(flags, 6),
x_flip: get_bit(flags, 5),
palette: byte_to_palette(self.memory.get(palette_addr)),
},
});
if objs.len() >= 10 {
break;
}
}
}
objs
}
fn render_object(&mut self, scanline: u8, object: Object, obj_size: &ObjSize) {
let mut object_row = scanline - object.y;
if object.flags.y_flip {
object_row = obj_size.get_height() - object_row;
}
let tile_row = object_row % 8;
let tile_addr = TiledataArea::D8000
.get_addr(object.tile_index + if object_row >= 8 { 1 } else { 0 })
+ (tile_row as u16 * 2);
let lsbs = self.memory.get(tile_addr);
let msbs = self.memory.get(tile_addr + 1);
for px_x in 0..8 {
let x_addr = if object.flags.x_flip { px_x } else { 7 - px_x };
let lsb = get_bit(lsbs, x_addr);
let msb = get_bit(msbs, x_addr);
let colour = bits_to_mapped_colour(lsb, msb, object.flags.palette);
let x_coord = (object.x as usize) + (px_x as usize);
2023-02-06 19:17:52 +11:00
if x_coord < WIDTH {
self.gpu.buffer[(scanline as usize * WIDTH) + x_coord] = colour.to_rgb();
}
}
}
2023-02-05 22:37:49 +11:00
2023-02-05 23:07:55 +11:00
fn render_tiles(
&mut self,
scanline: u8,
tilemap: &TilemapArea,
tiledata: &TiledataArea,
palette: Palette,
_offset_x: u8,
2023-02-05 23:07:55 +11:00
offset_y: u8,
) {
let tile_line = (scanline as usize) + (offset_y as usize);
2023-02-06 11:42:45 +11:00
let tilemap_row = tile_line / 8;
let tile_px = (tile_line) % 8;
let tiledata_offset = tile_px * 2;
2023-02-08 10:09:24 +11:00
let row_addr = ((tilemap_row * 32) % 0x400) as u16;
for x in 0..32_u16 {
let tile_addr = tiledata.get_addr(self.memory.get(tilemap.get_addr(row_addr + x)))
+ tiledata_offset as u16;
2023-02-06 11:42:45 +11:00
let lsbs = self.memory.get(tile_addr);
let msbs = self.memory.get(tile_addr + 1);
2023-02-05 22:37:49 +11:00
for px_x in 0..8 {
let lsb = get_bit(lsbs, 7 - px_x);
let msb = get_bit(msbs, 7 - px_x);
2023-02-05 22:37:49 +11:00
let colour = bits_to_mapped_colour(lsb, msb, palette);
let x_coord = ((x * 8) + (px_x as u16)) as usize;
if x_coord < WIDTH {
self.gpu.buffer[(scanline as usize * WIDTH) + x_coord] = colour.to_rgb();
}
}
}
}
2023-02-05 18:46:55 +11:00
fn render_window(&mut self) {
2023-02-05 22:56:18 +11:00
self.gpu.scaled_buffer = scale_buffer(&self.gpu.buffer, WIDTH, HEIGHT, FACTOR);
2023-02-05 18:46:55 +11:00
self.window
2023-02-05 22:56:18 +11:00
.update_with_buffer(&self.gpu.scaled_buffer, WIDTH * FACTOR, HEIGHT * FACTOR)
2023-02-05 18:46:55 +11:00
.unwrap();
}
}
2023-02-05 22:37:49 +11:00
2023-02-05 22:56:18 +11:00
fn scale_buffer(buffer: &Vec<u32>, width: usize, height: usize, factor: usize) -> Vec<u32> {
let mut v = vec![];
for y in 0..height {
for _ in 0..factor {
for x in 0..width {
for _ in 0..factor {
v.push(buffer[(y * width) + x]);
}
}
}
}
v
}
2023-02-05 22:37:49 +11:00
fn bits_to_mapped_colour(lsb: bool, msb: bool, palette: Palette) -> Colour {
match (lsb, msb) {
(true, true) => palette.three,
(true, false) => palette.one,
(false, true) => palette.two,
(false, false) => palette.zero,
}
}
fn byte_to_palette(byte: u8) -> Palette {
Palette {
zero: bits_to_colour(get_bit(byte, 0), get_bit(byte, 1)),
one: bits_to_colour(get_bit(byte, 2), get_bit(byte, 3)),
two: bits_to_colour(get_bit(byte, 4), get_bit(byte, 5)),
three: bits_to_colour(get_bit(byte, 6), get_bit(byte, 7)),
}
}
fn bits_to_colour(first: bool, second: bool) -> Colour {
match (first, second) {
(true, true) => Colour::Black,
2023-02-08 10:30:21 +11:00
(true, false) => Colour::LightGray,
(false, true) => Colour::DarkGray,
2023-02-05 22:37:49 +11:00
(false, false) => Colour::White,
}
}