fully mmio'd gpu!
This commit is contained in:
parent
1e1e82a6e7
commit
cf6a14179d
|
@ -131,9 +131,7 @@ fn main() {
|
||||||
window.topmost(true);
|
window.topmost(true);
|
||||||
|
|
||||||
let mut cpu = Cpu::new(
|
let mut cpu = Cpu::new(
|
||||||
Memory::init(bootrom, rom, args.connect_serial),
|
Memory::init(bootrom, rom, window, args.connect_serial, args.tile_window),
|
||||||
window,
|
|
||||||
args.tile_window,
|
|
||||||
bootrom_enabled,
|
bootrom_enabled,
|
||||||
Gilrs::new().unwrap(),
|
Gilrs::new().unwrap(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use self::mmio::{Apu, Joypad, Serial};
|
use self::mmio::{Apu, Gpu, Joypad, Serial};
|
||||||
pub use self::rom::Rom;
|
pub use self::rom::Rom;
|
||||||
use crate::{processor::SplitRegister, util::set_bit, verbose_println, Cpu};
|
use crate::{processor::SplitRegister, util::set_bit, verbose_println, Cpu};
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
use minifb::Key;
|
use minifb::{Key, Window};
|
||||||
|
|
||||||
pub mod mmio;
|
pub mod mmio;
|
||||||
pub(crate) mod rom;
|
pub(crate) mod rom;
|
||||||
|
@ -13,23 +13,28 @@ pub(crate) type Address = u16;
|
||||||
pub struct Memory {
|
pub struct Memory {
|
||||||
bootrom: Option<Vec<u8>>,
|
bootrom: Option<Vec<u8>>,
|
||||||
rom: Rom,
|
rom: Rom,
|
||||||
vram: [u8; 8192],
|
|
||||||
ram: [u8; 8192],
|
ram: [u8; 8192],
|
||||||
switchable_ram: [u8; 8192],
|
switchable_ram: [u8; 8192],
|
||||||
cpu_ram: [u8; 128],
|
cpu_ram: [u8; 128],
|
||||||
oam: [u8; 160],
|
|
||||||
interrupts: u8,
|
interrupts: u8,
|
||||||
pub(super) ime: bool,
|
pub(super) ime: bool,
|
||||||
pub(super) ime_scheduled: u8,
|
pub(super) ime_scheduled: u8,
|
||||||
io: [u8; 76],
|
io: [u8; 76],
|
||||||
pub(super) user_mode: bool,
|
pub(super) user_mode: bool,
|
||||||
joypad: Joypad,
|
joypad: Joypad,
|
||||||
|
gpu: Gpu,
|
||||||
apu: Apu,
|
apu: Apu,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Memory {
|
impl Memory {
|
||||||
pub fn init(bootrom: Option<Vec<u8>>, rom: Rom, connect_serial: bool) -> Self {
|
pub fn init(
|
||||||
|
bootrom: Option<Vec<u8>>,
|
||||||
|
rom: Rom,
|
||||||
|
window: Window,
|
||||||
|
connect_serial: bool,
|
||||||
|
enable_tile_window: bool,
|
||||||
|
) -> Self {
|
||||||
let serial = if connect_serial {
|
let serial = if connect_serial {
|
||||||
Serial::default().connected()
|
Serial::default().connected()
|
||||||
} else {
|
} else {
|
||||||
|
@ -38,17 +43,16 @@ impl Memory {
|
||||||
Self {
|
Self {
|
||||||
bootrom,
|
bootrom,
|
||||||
rom,
|
rom,
|
||||||
vram: [0x0; 8192],
|
|
||||||
ram: [0x0; 8192],
|
ram: [0x0; 8192],
|
||||||
switchable_ram: [0x0; 8192],
|
switchable_ram: [0x0; 8192],
|
||||||
cpu_ram: [0x0; 128],
|
cpu_ram: [0x0; 128],
|
||||||
oam: [0x0; 160],
|
|
||||||
interrupts: 0x0,
|
interrupts: 0x0,
|
||||||
ime: false,
|
ime: false,
|
||||||
ime_scheduled: 0x0,
|
ime_scheduled: 0x0,
|
||||||
io: [0xFF; 76],
|
io: [0xFF; 76],
|
||||||
user_mode: false,
|
user_mode: false,
|
||||||
joypad: Joypad::default(),
|
joypad: Joypad::default(),
|
||||||
|
gpu: Gpu::new(window, enable_tile_window),
|
||||||
apu: Apu::init_default(),
|
apu: Apu::init_default(),
|
||||||
serial,
|
serial,
|
||||||
}
|
}
|
||||||
|
@ -65,14 +69,14 @@ impl Memory {
|
||||||
self.rom.get(address)
|
self.rom.get(address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x8000..0xA000 => self.vram[(address - 0x8000) as usize],
|
0x8000..0xA000 => self.gpu.vram.get(address),
|
||||||
0xA000..0xC000 => {
|
0xA000..0xC000 => {
|
||||||
// cart ram
|
// cart ram
|
||||||
self.rom.get_ram(address)
|
self.rom.get_ram(address)
|
||||||
}
|
}
|
||||||
0xC000..0xE000 => self.ram[(address - 0xC000) as usize],
|
0xC000..0xE000 => self.ram[(address - 0xC000) as usize],
|
||||||
0xE000..0xFE00 => self.ram[(address - 0xE000) as usize],
|
0xE000..0xFE00 => self.ram[(address - 0xE000) as usize],
|
||||||
0xFE00..0xFEA0 => self.oam[(address - 0xFE00) as usize],
|
0xFE00..0xFEA0 => self.gpu.oam.get(address),
|
||||||
0xFEA0..0xFF00 => 0xFF,
|
0xFEA0..0xFF00 => 0xFF,
|
||||||
0xFF00..0xFF4C => self.get_io(address),
|
0xFF00..0xFF4C => self.get_io(address),
|
||||||
0xFF4C..0xFF80 => 0xFF,
|
0xFF4C..0xFF80 => 0xFF,
|
||||||
|
@ -87,11 +91,11 @@ impl Memory {
|
||||||
// change this with MBC code...
|
// change this with MBC code...
|
||||||
self.rom.set(address, data);
|
self.rom.set(address, data);
|
||||||
}
|
}
|
||||||
0x8000..0xA000 => self.vram[(address - 0x8000) as usize] = data,
|
0x8000..0xA000 => self.gpu.vram.set(address, data),
|
||||||
0xA000..0xC000 => self.rom.set_ram(address, data),
|
0xA000..0xC000 => self.rom.set_ram(address, data),
|
||||||
0xC000..0xE000 => self.ram[(address - 0xC000) as usize] = data,
|
0xC000..0xE000 => self.ram[(address - 0xC000) as usize] = data,
|
||||||
0xE000..0xFE00 => self.ram[(address - 0xE000) as usize] = data,
|
0xE000..0xFE00 => self.ram[(address - 0xE000) as usize] = data,
|
||||||
0xFE00..0xFEA0 => self.oam[(address - 0xFE00) as usize] = data,
|
0xFE00..0xFEA0 => self.gpu.oam.set(address, data),
|
||||||
0xFEA0..0xFF00 => {}
|
0xFEA0..0xFF00 => {}
|
||||||
0xFF00..0xFF4C => self.set_io(address, data),
|
0xFF00..0xFF4C => self.set_io(address, data),
|
||||||
0xFF50 => self.bootrom = None,
|
0xFF50 => self.bootrom = None,
|
||||||
|
@ -112,6 +116,17 @@ impl Memory {
|
||||||
0xFF01 => self.serial.get_queued(),
|
0xFF01 => self.serial.get_queued(),
|
||||||
0xFF02 => self.serial.get_control(),
|
0xFF02 => self.serial.get_control(),
|
||||||
0xFF10..0xFF40 => self.apu.get_register(address),
|
0xFF10..0xFF40 => self.apu.get_register(address),
|
||||||
|
0xFF40 => self.gpu.get_lcdc(),
|
||||||
|
0xFF41 => self.gpu.get_lcd_status(),
|
||||||
|
0xFF42 => self.gpu.get_scy(),
|
||||||
|
0xFF43 => self.gpu.get_scx(),
|
||||||
|
0xFF44 => self.gpu.get_ly(),
|
||||||
|
0xFF45 => self.gpu.get_lyc(),
|
||||||
|
0xFF47 => self.gpu.get_bg_palette(),
|
||||||
|
0xFF48 => self.gpu.get_obj_palette_0(),
|
||||||
|
0xFF49 => self.gpu.get_obj_palette_1(),
|
||||||
|
0xFF4A => self.gpu.get_wy(),
|
||||||
|
0xFF4B => self.gpu.get_wx(),
|
||||||
_ => self.io[(address - 0xFF00) as usize],
|
_ => self.io[(address - 0xFF00) as usize],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,14 +148,11 @@ impl Memory {
|
||||||
0xFF07 => self.masked_io(addr_l, data, 0b111),
|
0xFF07 => self.masked_io(addr_l, data, 0b111),
|
||||||
0xFF0F => self.masked_io(addr_l, data, 0b11111),
|
0xFF0F => self.masked_io(addr_l, data, 0b11111),
|
||||||
0xFF10..0xFF40 => self.apu.mmio_write(address, data),
|
0xFF10..0xFF40 => self.apu.mmio_write(address, data),
|
||||||
0xFF41 => {
|
0xFF40 => self.gpu.update_lcdc(data),
|
||||||
// mixed read/write
|
0xFF41 => self.gpu.update_lcd_status(data),
|
||||||
self.masked_io(addr_l, data, 0b01111000);
|
0xFF42 => self.gpu.update_scy(data),
|
||||||
}
|
0xFF43 => self.gpu.update_scx(data),
|
||||||
0xFF03 | 0xFF08..0xFF0F | 0xFF44 => {
|
0xFF45 => self.gpu.update_lyc(data),
|
||||||
// read-only addresses
|
|
||||||
println!("BANNED write: {data:#X} to {address:#X}");
|
|
||||||
}
|
|
||||||
0xFF46 => {
|
0xFF46 => {
|
||||||
if data > 0xDF {
|
if data > 0xDF {
|
||||||
panic!("dma transfer out of bounds: {data:#X}");
|
panic!("dma transfer out of bounds: {data:#X}");
|
||||||
|
@ -150,9 +162,18 @@ impl Memory {
|
||||||
addr.set_high(data);
|
addr.set_high(data);
|
||||||
for l in 0x0..0xA0 {
|
for l in 0x0..0xA0 {
|
||||||
addr.set_low(l);
|
addr.set_low(l);
|
||||||
self.oam[l as usize] = self.get(addr);
|
self.gpu.oam.data[l as usize] = self.get(addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
0xFF47 => self.gpu.update_bg_palette(data),
|
||||||
|
0xFF48 => self.gpu.update_obj_palette_0(data),
|
||||||
|
0xFF49 => self.gpu.update_obj_palette_1(data),
|
||||||
|
0xFF4A => self.gpu.update_wy(data),
|
||||||
|
0xFF4B => self.gpu.update_wx(data),
|
||||||
|
0xFF03 | 0xFF08..0xFF0F | 0xFF44 => {
|
||||||
|
// read-only addresses
|
||||||
|
println!("BANNED write: {data:#X} to {address:#X}");
|
||||||
|
}
|
||||||
0x0..0xFF00 | 0xFF4C..=u16::MAX => panic!("passed wrong address to set_io"),
|
0x0..0xFF00 | 0xFF4C..=u16::MAX => panic!("passed wrong address to set_io"),
|
||||||
_ => self.io[addr_l] = data,
|
_ => self.io[addr_l] = data,
|
||||||
}
|
}
|
||||||
|
@ -204,6 +225,23 @@ impl Cpu {
|
||||||
if self.memory.serial.tick(steps) {
|
if self.memory.serial.tick(steps) {
|
||||||
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 3));
|
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let gpu_interrupts = self.memory.gpu.tick(steps);
|
||||||
|
|
||||||
|
if gpu_interrupts.vblank {
|
||||||
|
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0));
|
||||||
|
|
||||||
|
if self
|
||||||
|
.memory
|
||||||
|
.update_pressed_keys(self.memory.gpu.window.get_keys(), &mut self.gamepad_handler)
|
||||||
|
{
|
||||||
|
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if gpu_interrupts.lcd_stat {
|
||||||
|
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
use self::{
|
use self::{
|
||||||
tile_window::TileWindow,
|
tile_window::TileWindow,
|
||||||
types::{
|
types::{
|
||||||
Colour, DrawMode, Lcdc, ObjSize, Object, ObjectFlags, Palette, TiledataArea, TilemapArea,
|
Colour, DrawMode, GpuInterrupts, Lcdc, Oam, ObjPalette, ObjSize, Object, ObjectFlags,
|
||||||
|
Palette, Stat, TiledataArea, TilemapArea, Vram,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
processor::{Cpu, SplitRegister},
|
processor::SplitRegister,
|
||||||
util::{clear_bit, get_bit, set_bit, set_or_clear_bit},
|
util::{clear_bit, get_bit},
|
||||||
FACTOR, HEIGHT, WIDTH,
|
FACTOR, HEIGHT, WIDTH,
|
||||||
};
|
};
|
||||||
use minifb::{Window, WindowOptions};
|
use minifb::{Window, WindowOptions};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
mod addresses;
|
||||||
mod tile_window;
|
mod tile_window;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
|
@ -31,17 +33,29 @@ pub fn init_statics() {
|
||||||
|
|
||||||
pub struct Gpu {
|
pub struct Gpu {
|
||||||
pub buffer: Vec<u32>,
|
pub buffer: Vec<u32>,
|
||||||
|
pub vram: Vram,
|
||||||
|
pub oam: Oam,
|
||||||
|
pub window: Window,
|
||||||
scaled_buffer: Vec<u32>,
|
scaled_buffer: Vec<u32>,
|
||||||
mode: DrawMode,
|
lcdc: Lcdc,
|
||||||
|
stat: Stat,
|
||||||
mode_clock: usize,
|
mode_clock: usize,
|
||||||
scanline: u8,
|
scanline: u8,
|
||||||
|
lyc: u8,
|
||||||
tile_window: Option<TileWindow>,
|
tile_window: Option<TileWindow>,
|
||||||
window_lc: u8,
|
window_lc: u8,
|
||||||
has_window_been_enabled: bool,
|
has_window_been_enabled: bool,
|
||||||
|
bg_palette: Palette,
|
||||||
|
obj_palette_0: Palette,
|
||||||
|
obj_palette_1: Palette,
|
||||||
|
scx: u8,
|
||||||
|
scy: u8,
|
||||||
|
wx: u8,
|
||||||
|
wy: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gpu {
|
impl Gpu {
|
||||||
pub fn new(enable_tile_window: bool) -> Self {
|
pub fn new(window: Window, enable_tile_window: bool) -> Self {
|
||||||
let tile_window = if enable_tile_window {
|
let tile_window = if enable_tile_window {
|
||||||
let mut window = Window::new(
|
let mut window = Window::new(
|
||||||
"Tiles",
|
"Tiles",
|
||||||
|
@ -62,41 +76,52 @@ impl Gpu {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
buffer: vec![0; WIDTH * HEIGHT],
|
buffer: vec![0; WIDTH * HEIGHT],
|
||||||
|
vram: Vram::default(),
|
||||||
|
oam: Oam::default(),
|
||||||
|
window,
|
||||||
scaled_buffer: vec![0; WIDTH * HEIGHT * 4],
|
scaled_buffer: vec![0; WIDTH * HEIGHT * 4],
|
||||||
mode: DrawMode::Mode2,
|
lcdc: Lcdc::default(),
|
||||||
|
stat: Stat::default(),
|
||||||
mode_clock: 0,
|
mode_clock: 0,
|
||||||
scanline: 0,
|
scanline: 0,
|
||||||
|
lyc: 0xFF,
|
||||||
tile_window,
|
tile_window,
|
||||||
window_lc: 0,
|
window_lc: 0,
|
||||||
has_window_been_enabled: false,
|
has_window_been_enabled: false,
|
||||||
|
bg_palette: Palette::from_byte(0xFC),
|
||||||
|
obj_palette_0: Palette::from_byte(0xFF),
|
||||||
|
obj_palette_1: Palette::from_byte(0xFF),
|
||||||
|
scx: 0,
|
||||||
|
scy: 0,
|
||||||
|
wx: 0,
|
||||||
|
wy: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Cpu {
|
pub fn tick(&mut self, steps: usize) -> GpuInterrupts {
|
||||||
pub fn advance_gpu_clock(&mut self, steps: usize) {
|
let mut interrupts = GpuInterrupts::default();
|
||||||
let lcdc = self.get_lcdc();
|
if self.lcdc.enable {
|
||||||
if lcdc.enable {
|
self.mode_clock += steps;
|
||||||
self.gpu.mode_clock += steps;
|
match self.stat.mode {
|
||||||
match self.gpu.mode {
|
|
||||||
DrawMode::HBlank => {
|
DrawMode::HBlank => {
|
||||||
// mode 0: hblank
|
// mode 0: hblank
|
||||||
if self.gpu.mode_clock >= 204 {
|
if self.mode_clock >= 204 {
|
||||||
self.gpu.mode_clock = 0;
|
self.mode_clock = 0;
|
||||||
self.gpu.scanline += 1;
|
self.scanline += 1;
|
||||||
if self.gpu.scanline == 143 {
|
if self.scanline == 143 {
|
||||||
self.enter_vblank();
|
self.enter_vblank();
|
||||||
|
interrupts.vblank = true;
|
||||||
} else {
|
} else {
|
||||||
self.gpu.mode = DrawMode::Mode2;
|
self.stat.mode = DrawMode::Mode2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DrawMode::VBlank => {
|
DrawMode::VBlank => {
|
||||||
// mode 1: vblank
|
// mode 1: vblank
|
||||||
if self.gpu.mode_clock >= 456 {
|
if self.mode_clock >= 456 {
|
||||||
self.gpu.mode_clock = 0;
|
self.mode_clock = 0;
|
||||||
self.gpu.scanline += 1;
|
self.scanline += 1;
|
||||||
if self.gpu.scanline == 153 {
|
if self.scanline == 153 {
|
||||||
self.exit_vblank();
|
self.exit_vblank();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,206 +129,124 @@ impl Cpu {
|
||||||
DrawMode::Mode2 => {
|
DrawMode::Mode2 => {
|
||||||
// search oam for sprites on this line
|
// search oam for sprites on this line
|
||||||
// we dont really have to emulate this
|
// we dont really have to emulate this
|
||||||
if self.gpu.mode_clock >= 80 {
|
if self.mode_clock >= 80 {
|
||||||
self.gpu.mode_clock = 0;
|
self.mode_clock = 0;
|
||||||
self.gpu.mode = DrawMode::Mode3;
|
self.stat.mode = DrawMode::Mode3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DrawMode::Mode3 => {
|
DrawMode::Mode3 => {
|
||||||
// generate scanline
|
// generate scanline
|
||||||
if self.gpu.mode_clock >= 172 {
|
if self.mode_clock >= 172 {
|
||||||
self.gpu.mode_clock = 0;
|
self.mode_clock = 0;
|
||||||
self.enter_hblank(&lcdc);
|
self.enter_hblank();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.gpu.mode_clock = 0;
|
self.mode_clock = 0;
|
||||||
self.gpu.mode = DrawMode::VBlank;
|
self.stat.mode = DrawMode::VBlank;
|
||||||
self.gpu.scanline = 0;
|
self.scanline = 0;
|
||||||
}
|
}
|
||||||
self.set_lcd_status();
|
|
||||||
|
interrupts.lcd_stat = (self.stat.lyc_eq_ly_interrupt_enabled
|
||||||
|
&& (self.lyc == self.scanline))
|
||||||
|
|| (self.stat.mode_2_interrupt_enabled && (self.stat.mode == DrawMode::Mode2))
|
||||||
|
|| (self.stat.vblank_interrupt_enabled && (self.stat.mode == DrawMode::VBlank))
|
||||||
|
|| (self.stat.hblank_interrupt_enabled && (self.stat.mode == DrawMode::HBlank));
|
||||||
|
|
||||||
|
interrupts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_lcdc(&self) -> Lcdc {
|
fn enter_hblank(&mut self) {
|
||||||
let reg = self.memory.get(0xFF40);
|
self.stat.mode = DrawMode::HBlank;
|
||||||
Lcdc {
|
self.render_scanline(self.scanline);
|
||||||
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 {
|
|
||||||
TiledataArea::D9000
|
|
||||||
},
|
|
||||||
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) {
|
|
||||||
self.gpu.mode = DrawMode::HBlank;
|
|
||||||
self.render_scanline(self.gpu.scanline, lcdc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_vblank(&mut self) {
|
fn enter_vblank(&mut self) {
|
||||||
if self
|
self.stat.mode = DrawMode::VBlank;
|
||||||
.memory
|
|
||||||
.update_pressed_keys(self.window.get_keys(), &mut self.gamepad_handler)
|
|
||||||
{
|
|
||||||
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 4));
|
|
||||||
}
|
|
||||||
self.gpu.mode = DrawMode::VBlank;
|
|
||||||
self.render_window();
|
self.render_window();
|
||||||
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_vblank(&mut self) {
|
fn exit_vblank(&mut self) {
|
||||||
self.gpu.mode = DrawMode::Mode2;
|
self.stat.mode = DrawMode::Mode2;
|
||||||
self.gpu.scanline = 0;
|
self.scanline = 0;
|
||||||
self.gpu.window_lc = 0;
|
self.window_lc = 0;
|
||||||
self.gpu.has_window_been_enabled = false;
|
self.has_window_been_enabled = false;
|
||||||
if let Some(tile_window) = &mut self.gpu.tile_window {
|
if let Some(tile_window) = &mut self.tile_window {
|
||||||
tile_window.draw_sprite_window(byte_to_palette(self.memory.get(0xFF47)), &self.memory);
|
tile_window.draw_sprite_window(self.bg_palette, &self.vram);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_lcd_status(&mut self) {
|
fn render_scanline(&mut self, scanline: u8) {
|
||||||
let mut stat = self.memory.get(0xFF41);
|
|
||||||
|
|
||||||
let lyc_eq_ly_enabled = get_bit(stat, 6);
|
|
||||||
let mode_2_enabled = get_bit(stat, 5);
|
|
||||||
let mode_1_vblank_enabled = get_bit(stat, 4);
|
|
||||||
let mode_0_hblank_enabled = get_bit(stat, 3);
|
|
||||||
|
|
||||||
let lyc_eq_ly = self.gpu.scanline == self.memory.get(0xFF45);
|
|
||||||
let mode_2 = self.gpu.mode == DrawMode::Mode2;
|
|
||||||
let mode_1_vblank = self.gpu.mode == DrawMode::VBlank;
|
|
||||||
let mode_0_hblank = self.gpu.mode == DrawMode::HBlank;
|
|
||||||
|
|
||||||
if (lyc_eq_ly_enabled && lyc_eq_ly)
|
|
||||||
|| (mode_2_enabled && mode_2)
|
|
||||||
|| (mode_1_vblank_enabled && mode_1_vblank)
|
|
||||||
|| (mode_0_hblank_enabled && mode_0_hblank)
|
|
||||||
{
|
|
||||||
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 1));
|
|
||||||
} else {
|
|
||||||
self.memory
|
|
||||||
.set(0xFF0F, clear_bit(self.memory.get(0xFF0F), 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
stat = set_or_clear_bit(stat, 2, lyc_eq_ly);
|
|
||||||
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);
|
|
||||||
self.memory.set(0xFF44, self.gpu.scanline);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_scanline(&mut self, scanline: u8, lcdc: &Lcdc) {
|
|
||||||
for x in 0..WIDTH {
|
for x in 0..WIDTH {
|
||||||
self.gpu.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 0, 255);
|
self.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 0, 255);
|
||||||
}
|
}
|
||||||
let bg_palette = byte_to_palette(self.memory.get(0xFF47));
|
if self.lcdc.bg_window_enable {
|
||||||
if lcdc.bg_window_enable {
|
self.render_scanline_bg(scanline);
|
||||||
self.render_scanline_bg(scanline, lcdc, bg_palette);
|
if self.lcdc.window_enable {
|
||||||
if lcdc.window_enable {
|
if !self.has_window_been_enabled {
|
||||||
if !self.gpu.has_window_been_enabled {
|
self.window_lc = scanline;
|
||||||
self.gpu.window_lc = scanline;
|
|
||||||
}
|
}
|
||||||
self.render_scanline_window(scanline, lcdc, bg_palette);
|
self.render_scanline_window(scanline);
|
||||||
self.gpu.has_window_been_enabled = true;
|
self.has_window_been_enabled = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for x in 0..WIDTH {
|
for x in 0..WIDTH {
|
||||||
self.gpu.buffer[(scanline as usize * WIDTH) + x] =
|
self.buffer[(scanline as usize * WIDTH) + x] = Colour::from_u8_rgb(255, 255, 255);
|
||||||
Colour::from_u8_rgb(255, 255, 255);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lcdc.obj_enable {
|
if self.lcdc.obj_enable {
|
||||||
self.render_scanline_obj(scanline, lcdc, bg_palette);
|
self.render_scanline_obj(scanline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_scanline_bg(&mut self, scanline: u8, lcdc: &Lcdc, palette: Palette) {
|
fn render_scanline_bg(&mut self, scanline: u8) {
|
||||||
let scroll_y = 0_u8.wrapping_sub(self.memory.get(0xFF42));
|
|
||||||
let scroll_x = 0_u8.wrapping_sub(self.memory.get(0xFF43));
|
|
||||||
self.render_tiles(
|
self.render_tiles(
|
||||||
scanline,
|
scanline,
|
||||||
scanline,
|
scanline,
|
||||||
&lcdc.bg_tilemap,
|
self.lcdc.bg_tilemap,
|
||||||
&lcdc.tile_area,
|
0_u8.wrapping_sub(self.scx),
|
||||||
palette,
|
0_u8.wrapping_sub(self.scy),
|
||||||
scroll_x,
|
|
||||||
scroll_y,
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_scanline_window(&mut self, scanline: u8, lcdc: &Lcdc, palette: Palette) {
|
fn render_scanline_window(&mut self, scanline: u8) {
|
||||||
let pos_y = self.memory.get(0xFF4A);
|
if self.wy < 143 && self.wx < 166 {
|
||||||
// subtracting 7 to get the Real Number...
|
|
||||||
let pos_x = self.memory.get(0xFF4B);
|
|
||||||
|
|
||||||
if pos_y < 143 && pos_x < 166 {
|
|
||||||
// window is on screen
|
// window is on screen
|
||||||
self.render_tiles(
|
self.render_tiles(
|
||||||
scanline,
|
scanline,
|
||||||
self.gpu.window_lc,
|
self.window_lc,
|
||||||
&lcdc.window_tilemap,
|
self.lcdc.window_tilemap,
|
||||||
&lcdc.tile_area,
|
self.wx.wrapping_sub(7),
|
||||||
palette,
|
self.wy,
|
||||||
pos_x.wrapping_sub(7),
|
|
||||||
pos_y,
|
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.gpu.window_lc = self.gpu.window_lc.wrapping_add(1);
|
self.window_lc = self.window_lc.wrapping_add(1);
|
||||||
self.gpu.has_window_been_enabled = true;
|
self.has_window_been_enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_scanline_obj(&mut self, scanline: u8, lcdc: &Lcdc, bg_palette: Palette) {
|
fn render_scanline_obj(&mut self, scanline: u8) {
|
||||||
let objs = self.parse_oam(scanline, &lcdc.obj_size);
|
let objs = self.parse_oam(scanline);
|
||||||
for object in objs {
|
for object in objs {
|
||||||
self.render_object(scanline, object, &lcdc.obj_size, bg_palette);
|
self.render_object(scanline, object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_oam(&mut self, scanline: u8, obj_size: &ObjSize) -> Vec<Object> {
|
fn parse_oam(&mut self, scanline: u8) -> Vec<Object> {
|
||||||
let mut objs = vec![];
|
let mut objs = vec![];
|
||||||
for i in (0xFE00..0xFE9F).step_by(4) {
|
for i in (0xFE00..0xFE9F).step_by(4) {
|
||||||
let y_pos = self.memory.get(i).wrapping_sub(16);
|
let y_pos = self.oam.get(i).wrapping_sub(16);
|
||||||
if y_pos <= scanline && (y_pos + obj_size.get_height()) > scanline {
|
if y_pos <= scanline && (y_pos + self.lcdc.obj_size.get_height()) > scanline {
|
||||||
// sprite is on line
|
// sprite is on line
|
||||||
let x_pos = self.memory.get(i + 1);
|
let x_pos = self.oam.get(i + 1);
|
||||||
let mut tile_index = self.memory.get(i + 2);
|
let mut tile_index = self.oam.get(i + 2);
|
||||||
if *obj_size == ObjSize::S8x16 {
|
if self.lcdc.obj_size == ObjSize::S8x16 {
|
||||||
tile_index = clear_bit(tile_index, 0);
|
tile_index = clear_bit(tile_index, 0);
|
||||||
}
|
}
|
||||||
let flags = self.memory.get(i + 3);
|
let flags = self.oam.get(i + 3);
|
||||||
let palette_addr = if get_bit(flags, 4) { 0xFF49 } else { 0xFF48 };
|
|
||||||
objs.push(Object {
|
objs.push(Object {
|
||||||
x: x_pos,
|
x: x_pos,
|
||||||
y: y_pos,
|
y: y_pos,
|
||||||
|
@ -312,7 +255,11 @@ impl Cpu {
|
||||||
behind_bg_and_window: get_bit(flags, 7),
|
behind_bg_and_window: get_bit(flags, 7),
|
||||||
y_flip: get_bit(flags, 6),
|
y_flip: get_bit(flags, 6),
|
||||||
x_flip: get_bit(flags, 5),
|
x_flip: get_bit(flags, 5),
|
||||||
palette: byte_to_palette(self.memory.get(palette_addr)),
|
palette: if get_bit(flags, 4) {
|
||||||
|
ObjPalette::One
|
||||||
|
} else {
|
||||||
|
ObjPalette::Zero
|
||||||
|
},
|
||||||
},
|
},
|
||||||
oam_location: (i - 0xFE00) as u8,
|
oam_location: (i - 0xFE00) as u8,
|
||||||
});
|
});
|
||||||
|
@ -329,29 +276,33 @@ impl Cpu {
|
||||||
objs
|
objs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_object(
|
fn render_object(&mut self, scanline: u8, object: Object) {
|
||||||
&mut self,
|
|
||||||
scanline: u8,
|
|
||||||
object: Object,
|
|
||||||
obj_size: &ObjSize,
|
|
||||||
bg_palette: Palette,
|
|
||||||
) {
|
|
||||||
let mut object_row = scanline - object.y;
|
let mut object_row = scanline - object.y;
|
||||||
if object.flags.y_flip {
|
if object.flags.y_flip {
|
||||||
object_row = obj_size.get_height() - (object_row + 1);
|
object_row = self.lcdc.obj_size.get_height() - (object_row + 1);
|
||||||
}
|
}
|
||||||
let tile_row = object_row % 8;
|
let tile_row = object_row % 8;
|
||||||
let tile_addr = TiledataArea::D8000
|
let tile_addr = TiledataArea::D8000
|
||||||
.get_addr(object.tile_index + if object_row >= 8 { 1 } else { 0 })
|
.get_addr(object.tile_index + if object_row >= 8 { 1 } else { 0 })
|
||||||
+ (tile_row as u16 * 2);
|
+ (tile_row as u16 * 2);
|
||||||
let lsbs = self.memory.get(tile_addr);
|
let lsbs = self.vram.get(tile_addr);
|
||||||
let msbs = self.memory.get(tile_addr + 1);
|
let msbs = self.vram.get(tile_addr + 1);
|
||||||
for px_x in 0..8 {
|
for px_x in 0..8 {
|
||||||
let x_addr = if object.flags.x_flip { px_x } else { 7 - px_x };
|
let x_addr = if object.flags.x_flip { px_x } else { 7 - px_x };
|
||||||
let lsb = get_bit(lsbs, x_addr);
|
let lsb = get_bit(lsbs, x_addr);
|
||||||
let msb = get_bit(msbs, x_addr);
|
let msb = get_bit(msbs, x_addr);
|
||||||
let colour = bits_to_mapped_colour(lsb, msb, object.flags.palette);
|
let colour = match object.flags.palette {
|
||||||
if colour == object.flags.palette.zero {
|
ObjPalette::Zero => self.obj_palette_0,
|
||||||
|
ObjPalette::One => self.obj_palette_1,
|
||||||
|
}
|
||||||
|
.map_bits(lsb, msb);
|
||||||
|
if colour
|
||||||
|
== match object.flags.palette {
|
||||||
|
ObjPalette::Zero => self.obj_palette_0,
|
||||||
|
ObjPalette::One => self.obj_palette_1,
|
||||||
|
}
|
||||||
|
.zero
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let x_coord_uncorrected = (object.x as usize) + (px_x as usize);
|
let x_coord_uncorrected = (object.x as usize) + (px_x as usize);
|
||||||
|
@ -362,9 +313,9 @@ impl Cpu {
|
||||||
if x_coord < WIDTH {
|
if x_coord < WIDTH {
|
||||||
let buffer_index = (scanline as usize * WIDTH) + x_coord;
|
let buffer_index = (scanline as usize * WIDTH) + x_coord;
|
||||||
if !object.flags.behind_bg_and_window
|
if !object.flags.behind_bg_and_window
|
||||||
|| self.gpu.buffer[buffer_index] == bg_palette.zero.as_rgb()
|
|| self.buffer[buffer_index] == self.bg_palette.zero.as_rgb()
|
||||||
{
|
{
|
||||||
self.gpu.buffer[buffer_index] = colour.as_rgb();
|
self.buffer[buffer_index] = colour.as_rgb();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -375,9 +326,7 @@ impl Cpu {
|
||||||
&mut self,
|
&mut self,
|
||||||
scanline: u8,
|
scanline: u8,
|
||||||
draw_from: u8,
|
draw_from: u8,
|
||||||
tilemap: &TilemapArea,
|
tilemap: TilemapArea,
|
||||||
tiledata: &TiledataArea,
|
|
||||||
palette: Palette,
|
|
||||||
offset_x: u8,
|
offset_x: u8,
|
||||||
offset_y: u8,
|
offset_y: u8,
|
||||||
wrap: bool,
|
wrap: bool,
|
||||||
|
@ -399,27 +348,27 @@ impl Cpu {
|
||||||
let tilemap_column = (tile_line_x / 8) as u16;
|
let tilemap_column = (tile_line_x / 8) as u16;
|
||||||
|
|
||||||
let tile_px_x = tile_line_x % 8;
|
let tile_px_x = tile_line_x % 8;
|
||||||
let tile_addr = tiledata.get_addr(
|
let tile_addr = self
|
||||||
self.memory
|
.lcdc
|
||||||
.get(tilemap.get_addr(row_addr + (tilemap_column))),
|
.tile_area
|
||||||
) + tiledata_offset as u16;
|
.get_addr(self.vram.get(tilemap.get_addr(row_addr + (tilemap_column))))
|
||||||
|
+ tiledata_offset as u16;
|
||||||
|
|
||||||
let lsbs = self.memory.get(tile_addr);
|
let lsbs = self.vram.get(tile_addr);
|
||||||
let msbs = self.memory.get(tile_addr + 1);
|
let msbs = self.vram.get(tile_addr + 1);
|
||||||
let lsb = get_bit(lsbs, 7 - tile_px_x);
|
let lsb = get_bit(lsbs, 7 - tile_px_x);
|
||||||
let msb = get_bit(msbs, 7 - tile_px_x);
|
let msb = get_bit(msbs, 7 - tile_px_x);
|
||||||
let colour = bits_to_mapped_colour(lsb, msb, palette);
|
let colour = self.bg_palette.map_bits(lsb, msb);
|
||||||
|
|
||||||
self.gpu.buffer[(scanline as usize * WIDTH) + x] = colour.as_rgb();
|
self.buffer[(scanline as usize * WIDTH) + x] = colour.as_rgb();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_window(&mut self) {
|
fn render_window(&mut self) {
|
||||||
self.gpu.scaled_buffer =
|
self.scaled_buffer = scale_buffer(&self.buffer, WIDTH, HEIGHT, *FACTOR.get().unwrap());
|
||||||
scale_buffer(&self.gpu.buffer, WIDTH, HEIGHT, *FACTOR.get().unwrap());
|
|
||||||
self.window
|
self.window
|
||||||
.update_with_buffer(
|
.update_with_buffer(
|
||||||
&self.gpu.scaled_buffer,
|
&self.scaled_buffer,
|
||||||
WIDTH * FACTOR.get().unwrap(),
|
WIDTH * FACTOR.get().unwrap(),
|
||||||
HEIGHT * FACTOR.get().unwrap(),
|
HEIGHT * FACTOR.get().unwrap(),
|
||||||
)
|
)
|
||||||
|
@ -440,30 +389,3 @@ fn scale_buffer(buffer: &[u32], width: usize, height: usize, factor: usize) -> V
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
(true, false) => Colour::LightGray,
|
|
||||||
(false, true) => Colour::DarkGray,
|
|
||||||
(false, false) => Colour::White,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
158
src/processor/memory/mmio/gpu/addresses.rs
Normal file
158
src/processor/memory/mmio/gpu/addresses.rs
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
use crate::util::{get_bit, set_or_clear_bit};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
types::{DrawMode, ObjSize, Palette, TiledataArea, TilemapArea},
|
||||||
|
Gpu,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Gpu {
|
||||||
|
pub fn update_lcdc(&mut self, data: u8) {
|
||||||
|
self.lcdc.enable = get_bit(data, 7);
|
||||||
|
self.lcdc.window_tilemap = if get_bit(data, 6) {
|
||||||
|
TilemapArea::T9C00
|
||||||
|
} else {
|
||||||
|
TilemapArea::T9800
|
||||||
|
};
|
||||||
|
self.lcdc.window_enable = get_bit(data, 5);
|
||||||
|
self.lcdc.tile_area = if get_bit(data, 4) {
|
||||||
|
TiledataArea::D8000
|
||||||
|
} else {
|
||||||
|
TiledataArea::D9000
|
||||||
|
};
|
||||||
|
self.lcdc.bg_tilemap = if get_bit(data, 3) {
|
||||||
|
TilemapArea::T9C00
|
||||||
|
} else {
|
||||||
|
TilemapArea::T9800
|
||||||
|
};
|
||||||
|
self.lcdc.obj_size = if get_bit(data, 2) {
|
||||||
|
ObjSize::S8x16
|
||||||
|
} else {
|
||||||
|
ObjSize::S8x8
|
||||||
|
};
|
||||||
|
self.lcdc.obj_enable = get_bit(data, 1);
|
||||||
|
self.lcdc.bg_window_enable = get_bit(data, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_lcdc(&self) -> u8 {
|
||||||
|
(if self.lcdc.enable { 1 } else { 0 }) << 7
|
||||||
|
| (match self.lcdc.window_tilemap {
|
||||||
|
TilemapArea::T9800 => 0,
|
||||||
|
TilemapArea::T9C00 => 1,
|
||||||
|
}) << 6
|
||||||
|
| (if self.lcdc.window_enable { 1 } else { 0 }) << 5
|
||||||
|
| (match self.lcdc.tile_area {
|
||||||
|
TiledataArea::D8000 => 1,
|
||||||
|
TiledataArea::D9000 => 0,
|
||||||
|
}) << 4
|
||||||
|
| (match self.lcdc.bg_tilemap {
|
||||||
|
TilemapArea::T9800 => 0,
|
||||||
|
TilemapArea::T9C00 => 1,
|
||||||
|
}) << 3
|
||||||
|
| (match self.lcdc.obj_size {
|
||||||
|
ObjSize::S8x8 => 0,
|
||||||
|
ObjSize::S8x16 => 1,
|
||||||
|
}) << 2
|
||||||
|
| (if self.lcdc.obj_enable { 1 } else { 0 }) << 1
|
||||||
|
| (if self.lcdc.bg_window_enable { 1 } else { 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_lcd_status(&mut self, data: u8) {
|
||||||
|
self.stat.lyc_eq_ly_interrupt_enabled = get_bit(data, 6);
|
||||||
|
self.stat.mode_2_interrupt_enabled = get_bit(data, 5);
|
||||||
|
self.stat.vblank_interrupt_enabled = get_bit(data, 4);
|
||||||
|
self.stat.hblank_interrupt_enabled = get_bit(data, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_lcd_status(&self) -> u8 {
|
||||||
|
let mut reg = 0xFF;
|
||||||
|
|
||||||
|
reg = set_or_clear_bit(reg, 6, self.stat.lyc_eq_ly_interrupt_enabled);
|
||||||
|
reg = set_or_clear_bit(reg, 5, self.stat.mode_2_interrupt_enabled);
|
||||||
|
reg = set_or_clear_bit(reg, 4, self.stat.vblank_interrupt_enabled);
|
||||||
|
reg = set_or_clear_bit(reg, 3, self.stat.hblank_interrupt_enabled);
|
||||||
|
|
||||||
|
let lyc_eq_ly = self.scanline == self.lyc;
|
||||||
|
reg = set_or_clear_bit(reg, 2, lyc_eq_ly);
|
||||||
|
|
||||||
|
reg = set_or_clear_bit(
|
||||||
|
reg,
|
||||||
|
1,
|
||||||
|
(self.stat.mode == DrawMode::Mode2) || (self.stat.mode == DrawMode::Mode3),
|
||||||
|
);
|
||||||
|
reg = set_or_clear_bit(
|
||||||
|
reg,
|
||||||
|
0,
|
||||||
|
(self.stat.mode == DrawMode::VBlank) || (self.stat.mode == DrawMode::Mode3),
|
||||||
|
);
|
||||||
|
|
||||||
|
reg
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_lyc(&mut self, data: u8) {
|
||||||
|
self.lyc = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_lyc(&self) -> u8 {
|
||||||
|
self.lyc
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ly(&self) -> u8 {
|
||||||
|
self.scanline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_bg_palette(&mut self, data: u8) {
|
||||||
|
self.bg_palette = Palette::from_byte(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bg_palette(&self) -> u8 {
|
||||||
|
self.bg_palette.as_byte()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_obj_palette_0(&mut self, data: u8) {
|
||||||
|
self.obj_palette_0 = Palette::from_byte(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_obj_palette_0(&self) -> u8 {
|
||||||
|
self.obj_palette_0.as_byte()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_obj_palette_1(&mut self, data: u8) {
|
||||||
|
self.obj_palette_1 = Palette::from_byte(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_obj_palette_1(&self) -> u8 {
|
||||||
|
self.obj_palette_1.as_byte()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_scx(&mut self, data: u8) {
|
||||||
|
self.scx = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_scy(&mut self, data: u8) {
|
||||||
|
self.scy = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_scx(&self) -> u8 {
|
||||||
|
self.scx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_scy(&self) -> u8 {
|
||||||
|
self.scy
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_wx(&mut self, data: u8) {
|
||||||
|
self.wx = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_wy(&mut self, data: u8) {
|
||||||
|
self.wy = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_wx(&self) -> u8 {
|
||||||
|
self.wx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_wy(&self) -> u8 {
|
||||||
|
self.wy
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,17 +3,16 @@ use minifb::Window;
|
||||||
use crate::{
|
use crate::{
|
||||||
processor::{
|
processor::{
|
||||||
get_bit,
|
get_bit,
|
||||||
memory::{
|
memory::mmio::gpu::{
|
||||||
mmio::gpu::{
|
scale_buffer, Palette, TiledataArea, TILE_WINDOW_HEIGHT, TILE_WINDOW_HEIGHT_SCALED,
|
||||||
bits_to_mapped_colour, scale_buffer, Palette, TiledataArea, TILE_WINDOW_HEIGHT,
|
TILE_WINDOW_WIDTH, TILE_WINDOW_WIDTH_SCALED,
|
||||||
TILE_WINDOW_HEIGHT_SCALED, TILE_WINDOW_WIDTH, TILE_WINDOW_WIDTH_SCALED,
|
|
||||||
},
|
|
||||||
Memory,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
FACTOR,
|
FACTOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::types::Vram;
|
||||||
|
|
||||||
pub(super) struct TileWindow {
|
pub(super) struct TileWindow {
|
||||||
sprite_buffer: Vec<u32>,
|
sprite_buffer: Vec<u32>,
|
||||||
sprite_buffer_scaled: Vec<u32>,
|
sprite_buffer_scaled: Vec<u32>,
|
||||||
|
@ -35,7 +34,7 @@ impl TileWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TileWindow {
|
impl TileWindow {
|
||||||
pub(super) fn draw_sprite_window(&mut self, palette: Palette, memory: &Memory) {
|
pub(super) fn draw_sprite_window(&mut self, palette: Palette, memory: &Vram) {
|
||||||
for tile_y in 0..16 {
|
for tile_y in 0..16 {
|
||||||
self.draw_row(
|
self.draw_row(
|
||||||
tile_y,
|
tile_y,
|
||||||
|
@ -76,7 +75,7 @@ impl TileWindow {
|
||||||
display_y: usize,
|
display_y: usize,
|
||||||
area: TiledataArea,
|
area: TiledataArea,
|
||||||
palette: Palette,
|
palette: Palette,
|
||||||
memory: &Memory,
|
memory: &Vram,
|
||||||
) {
|
) {
|
||||||
for tile_x in 0..16 {
|
for tile_x in 0..16 {
|
||||||
let tile_num = (tile_y * 16) + tile_x;
|
let tile_num = (tile_y * 16) + tile_x;
|
||||||
|
@ -89,7 +88,7 @@ impl TileWindow {
|
||||||
let real_px_x = (tile_x as usize * 8) + px_x as usize;
|
let real_px_x = (tile_x as usize * 8) + px_x as usize;
|
||||||
let lsb = get_bit(lsbs, 7 - px_x);
|
let lsb = get_bit(lsbs, 7 - px_x);
|
||||||
let msb = get_bit(msbs, 7 - px_x);
|
let msb = get_bit(msbs, 7 - px_x);
|
||||||
let colour = bits_to_mapped_colour(lsb, msb, palette);
|
let colour = palette.map_bits(lsb, msb);
|
||||||
|
|
||||||
self.sprite_buffer[real_px_x + (real_px_y * TILE_WINDOW_WIDTH)] =
|
self.sprite_buffer[real_px_x + (real_px_y * TILE_WINDOW_WIDTH)] =
|
||||||
colour.as_rgb();
|
colour.as_rgb();
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::util::as_signed;
|
use crate::{
|
||||||
|
processor::memory::Address,
|
||||||
|
util::{as_signed, get_bit},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub(super) enum DrawMode {
|
pub(super) enum DrawMode {
|
||||||
|
@ -65,6 +68,21 @@ pub(super) struct Lcdc {
|
||||||
pub(super) bg_window_enable: bool,
|
pub(super) bg_window_enable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Lcdc {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enable: true,
|
||||||
|
window_tilemap: TilemapArea::T9800,
|
||||||
|
window_enable: false,
|
||||||
|
tile_area: TiledataArea::D8000,
|
||||||
|
bg_tilemap: TilemapArea::T9800,
|
||||||
|
obj_size: ObjSize::S8x8,
|
||||||
|
obj_enable: false,
|
||||||
|
bg_window_enable: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub(super) enum Colour {
|
pub(super) enum Colour {
|
||||||
White,
|
White,
|
||||||
|
@ -87,6 +105,24 @@ impl Colour {
|
||||||
let (r, g, b) = (r as u32, g as u32, b as u32);
|
let (r, g, b) = (r as u32, g as u32, b as u32);
|
||||||
(r << 16) | (g << 8) | b
|
(r << 16) | (g << 8) | b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_bits(first: bool, second: bool) -> Colour {
|
||||||
|
match (first, second) {
|
||||||
|
(true, true) => Colour::Black,
|
||||||
|
(true, false) => Colour::LightGray,
|
||||||
|
(false, true) => Colour::DarkGray,
|
||||||
|
(false, false) => Colour::White,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_bits(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Colour::White => 0b00,
|
||||||
|
Colour::LightGray => 0b10,
|
||||||
|
Colour::DarkGray => 0b01,
|
||||||
|
Colour::Black => 0b11,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -97,11 +133,43 @@ pub(super) struct Palette {
|
||||||
pub(super) three: Colour,
|
pub(super) three: Colour,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Palette {
|
||||||
|
pub(super) fn from_byte(byte: u8) -> Palette {
|
||||||
|
Palette {
|
||||||
|
zero: Colour::from_bits(get_bit(byte, 0), get_bit(byte, 1)),
|
||||||
|
one: Colour::from_bits(get_bit(byte, 2), get_bit(byte, 3)),
|
||||||
|
two: Colour::from_bits(get_bit(byte, 4), get_bit(byte, 5)),
|
||||||
|
three: Colour::from_bits(get_bit(byte, 6), get_bit(byte, 7)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn as_byte(&self) -> u8 {
|
||||||
|
self.zero.as_bits()
|
||||||
|
| (self.one.as_bits() << 2)
|
||||||
|
| (self.two.as_bits() << 4)
|
||||||
|
| (self.three.as_bits() << 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn map_bits(&self, lsb: bool, msb: bool) -> Colour {
|
||||||
|
match (lsb, msb) {
|
||||||
|
(true, true) => self.three,
|
||||||
|
(true, false) => self.one,
|
||||||
|
(false, true) => self.two,
|
||||||
|
(false, false) => self.zero,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) struct ObjectFlags {
|
pub(super) struct ObjectFlags {
|
||||||
pub(super) behind_bg_and_window: bool,
|
pub(super) behind_bg_and_window: bool,
|
||||||
pub(super) y_flip: bool,
|
pub(super) y_flip: bool,
|
||||||
pub(super) x_flip: bool,
|
pub(super) x_flip: bool,
|
||||||
pub(super) palette: Palette,
|
pub(super) palette: ObjPalette,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) enum ObjPalette {
|
||||||
|
Zero,
|
||||||
|
One,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct Object {
|
pub(super) struct Object {
|
||||||
|
@ -111,3 +179,69 @@ pub(super) struct Object {
|
||||||
pub(super) flags: ObjectFlags,
|
pub(super) flags: ObjectFlags,
|
||||||
pub(super) oam_location: u8,
|
pub(super) oam_location: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) struct Stat {
|
||||||
|
pub(super) lyc_eq_ly_interrupt_enabled: bool,
|
||||||
|
pub(super) mode_2_interrupt_enabled: bool,
|
||||||
|
pub(super) vblank_interrupt_enabled: bool,
|
||||||
|
pub(super) hblank_interrupt_enabled: bool,
|
||||||
|
pub(super) mode: DrawMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Stat {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
lyc_eq_ly_interrupt_enabled: false,
|
||||||
|
mode_2_interrupt_enabled: false,
|
||||||
|
vblank_interrupt_enabled: false,
|
||||||
|
hblank_interrupt_enabled: false,
|
||||||
|
mode: DrawMode::Mode2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Vram {
|
||||||
|
data: [u8; 8192],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vram {
|
||||||
|
pub fn get(&self, address: Address) -> u8 {
|
||||||
|
self.data[(address - 0x8000) as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, address: Address, data: u8) {
|
||||||
|
self.data[(address - 0x8000) as usize] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Vram {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { data: [0x0; 8192] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Oam {
|
||||||
|
pub data: [u8; 160],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oam {
|
||||||
|
pub fn get(&self, address: Address) -> u8 {
|
||||||
|
self.data[(address - 0xFE00) as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, address: Address, data: u8) {
|
||||||
|
self.data[(address - 0xFE00) as usize] = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Oam {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { data: [0x0; 160] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GpuInterrupts {
|
||||||
|
pub lcd_stat: bool,
|
||||||
|
pub vblank: bool,
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use self::{
|
use self::{memory::Memory, timer::Timers};
|
||||||
memory::{mmio::Gpu, Memory},
|
|
||||||
timer::Timers,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
util::{clear_bit, get_bit},
|
util::{clear_bit, get_bit},
|
||||||
verbose_println,
|
verbose_println,
|
||||||
};
|
};
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
use minifb::Window;
|
|
||||||
|
|
||||||
mod instructions;
|
mod instructions;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
|
@ -34,8 +30,6 @@ pub struct Cpu {
|
||||||
pub reg: Registers,
|
pub reg: Registers,
|
||||||
pub last_instruction: u8,
|
pub last_instruction: u8,
|
||||||
last_instruction_addr: u16,
|
last_instruction_addr: u16,
|
||||||
window: Window,
|
|
||||||
gpu: Gpu,
|
|
||||||
halted: bool,
|
halted: bool,
|
||||||
timers: Timers,
|
timers: Timers,
|
||||||
gamepad_handler: Gilrs,
|
gamepad_handler: Gilrs,
|
||||||
|
@ -43,13 +37,7 @@ pub struct Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
pub fn new(
|
pub fn new(mut memory: Memory, run_bootrom: bool, gamepad_handler: Gilrs) -> Self {
|
||||||
mut memory: Memory,
|
|
||||||
window: Window,
|
|
||||||
enable_tile_window: bool,
|
|
||||||
run_bootrom: bool,
|
|
||||||
gamepad_handler: Gilrs,
|
|
||||||
) -> Self {
|
|
||||||
if !run_bootrom {
|
if !run_bootrom {
|
||||||
memory.cpu_ram_init();
|
memory.cpu_ram_init();
|
||||||
}
|
}
|
||||||
|
@ -58,8 +46,6 @@ impl Cpu {
|
||||||
reg: Registers::init(run_bootrom),
|
reg: Registers::init(run_bootrom),
|
||||||
last_instruction: 0x0,
|
last_instruction: 0x0,
|
||||||
last_instruction_addr: 0x0,
|
last_instruction_addr: 0x0,
|
||||||
window,
|
|
||||||
gpu: Gpu::new(enable_tile_window),
|
|
||||||
halted: false,
|
halted: false,
|
||||||
timers: Timers::init(),
|
timers: Timers::init(),
|
||||||
gamepad_handler,
|
gamepad_handler,
|
||||||
|
|
|
@ -27,7 +27,6 @@ impl Cpu {
|
||||||
pub(super) fn increment_timers(&mut self, machine_cycles: u8) {
|
pub(super) fn increment_timers(&mut self, machine_cycles: u8) {
|
||||||
let clock_cycles = (machine_cycles as usize) * 4;
|
let clock_cycles = (machine_cycles as usize) * 4;
|
||||||
|
|
||||||
self.advance_gpu_clock(clock_cycles);
|
|
||||||
self.advance_mmio_clocks(clock_cycles);
|
self.advance_mmio_clocks(clock_cycles);
|
||||||
|
|
||||||
self.timers.div_counter += clock_cycles;
|
self.timers.div_counter += clock_cycles;
|
||||||
|
|
Loading…
Reference in a new issue