diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..288d6d7b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,338 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "cmake" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855" +dependencies = [ + "cc", +] + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "fern" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" +dependencies = [ + "log", +] + +[[package]] +name = "filetime" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "flate2" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80edafed416a46fb378521624fab1cfa2eb514784fd8921adbe8a8d8321da811" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lib_gb" +version = "1.0.0" +dependencies = [ + "log", +] + +[[package]] +name = "libc" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "magenboy" +version = "1.0.0" +dependencies = [ + "chrono", + "fern", + "lib_gb", + "log", + "sdl2", + "wav", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "riff" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" + +[[package]] +name = "sdl2" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deecbc3fa9460acff5a1e563e05cb5f31bba0aa0c214bb49a43db8159176d54b" +dependencies = [ + "bitflags", + "lazy_static", + "libc", + "sdl2-sys", +] + +[[package]] +name = "sdl2-sys" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a29aa21f175b5a41a6e26da572d5e5d1ee5660d35f9f9d0913e8a802098f74" +dependencies = [ + "cfg-if 0.1.10", + "cmake", + "flate2", + "libc", + "tar", + "unidiff", + "version-compare", +] + +[[package]] +name = "tar" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f5515d3add52e0bbdcad7b83c388bb36ba7b754dda3b5f5bc2d38640cdba5c" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "unidiff" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a62719acf1933bfdbeb73a657ecd9ecece70b405125267dd549e2e2edc232c" +dependencies = [ + "encoding_rs", + "lazy_static", + "regex", +] + +[[package]] +name = "version-compare" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wav" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549df97e073e1f901a489f159eb451bbe9c790e2f217394f4ce01acda0380b0c" +dependencies = [ + "riff", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..5c90c705 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "gb", + "lib_gb" +] \ No newline at end of file diff --git a/README.md b/README.md index 4b74cce9..7dd2c8dd 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,5 @@ Curerently there is no Support (support is planned in the future) - [The GameBoy Programming Manual](http://index-of.es/Varios-2/Game%20Boy%20Programming%20Manual.pdf) - [gbdev gameboy sound hardware](https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware) - [Hactix's awsome blog post](https://hacktix.github.io/GBEDG/) -- [Nightshade's awsome blog post](https://nightshade256.github.io/2021/03/27/gb-sound-emulation.html) \ No newline at end of file +- [Nightshade's awsome blog post](https://nightshade256.github.io/2021/03/27/gb-sound-emulation.html) +- [The Ultimate GameBoy Talk](https://www.youtube.com/watch?v=HyzD8pNlpwI) \ No newline at end of file diff --git a/gb/src/main.rs b/gb/src/main.rs index 309774a7..2f738005 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -4,35 +4,17 @@ mod sdl_audio_device; mod audio_resampler; mod wav_file_audio_device; mod multi_device_audio; +mod sdl_gfx_device; use crate::{mbc_handler::*, sdl_joypad_provider::*, multi_device_audio::*}; use lib_gb::{keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, GB_FREQUENCY, apu::audio_device::*}; -use std::{ - ffi::{c_void, CString}, - fs, env, result::Result, vec::Vec -}; +use std::{fs, env, result::Result, vec::Vec}; use log::info; use sdl2::sys::*; const FPS:f64 = GB_FREQUENCY as f64 / 70224.0; const FRAME_TIME_MS:f64 = (1.0 / FPS) * 1000.0; - - -fn extend_vec(vec:&[u32], scale:usize, w:usize, h:usize)->Vec{ - let mut new_vec = vec![0;vec.len()*scale*scale]; - for y in 0..h{ - let sy = y*scale; - for x in 0..w{ - let sx = x*scale; - for i in 0..scale{ - for j in 0..scale{ - new_vec[(sy+i)*(w*scale)+sx+j] = vec[y*w+x]; - } - } - } - } - return new_vec; -} +const SCREEN_SCALE:u8 = 4; fn init_logger(debug:bool)->Result<(), fern::InitError>{ let level = if debug {log::LevelFilter::Debug} else {log::LevelFilter::Info}; @@ -77,8 +59,6 @@ fn check_for_terminal_feature_flag(args:&Vec::, flag:&str)->bool{ } fn main() { - let screen_scale:u32 = 4; - let args: Vec = env::args().collect(); let debug_level = check_for_terminal_feature_flag(&args, "--log"); @@ -88,25 +68,6 @@ fn main() { Result::Err(error)=>std::panic!("error initing logger: {}", error) } - let buffer_width = SCREEN_WIDTH as u32 * screen_scale; - let buffer_height = SCREEN_HEIGHT as u32* screen_scale; - let program_name = CString::new("MagenBoy").unwrap(); - let (_window, renderer, texture): (*mut SDL_Window, *mut SDL_Renderer, *mut SDL_Texture) = unsafe{ - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); - let wind:*mut SDL_Window = SDL_CreateWindow( - program_name.as_ptr(), - SDL_WINDOWPOS_UNDEFINED_MASK as i32, SDL_WINDOWPOS_UNDEFINED_MASK as i32, - buffer_width as i32, buffer_height as i32, 0); - - let rend: *mut SDL_Renderer = SDL_CreateRenderer(wind, -1, 0); - - let tex: *mut SDL_Texture = SDL_CreateTexture(rend, - SDL_PixelFormatEnum::SDL_PIXELFORMAT_ARGB8888 as u32, SDL_TextureAccess::SDL_TEXTUREACCESS_STREAMING as i32, - buffer_width as i32, buffer_height as i32); - - (wind, rend, tex) - }; - let audio_device = sdl_audio_device::SdlAudioDevie::new(44100); let mut devices: Vec::> = Vec::new(); devices.push(Box::new(audio_device)); @@ -117,11 +78,13 @@ fn main() { let audio_devices = MultiAudioDevice::new(devices); + let sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32, "MagenBoy", SCREEN_SCALE); + let program_name = &args[1]; let mut mbc = initialize_mbc(program_name); let joypad_provider = SdlJoypadProvider::new(buttons_mapper); - let mut gameboy = match fs::read("Dependencies\\Init\\dmg_boot.bin"){ + let mut gameboy = match fs::read("Dependencies/Init/dmg_boot.bin"){ Result::Ok(file)=>{ info!("found bootrom!"); @@ -130,12 +93,12 @@ fn main() { bootrom[i] = file[i]; } - GameBoy::new_with_bootrom(&mut mbc, joypad_provider,audio_devices, bootrom) + GameBoy::new_with_bootrom(&mut mbc, joypad_provider,audio_devices, sdl_gfx_device, bootrom) } Result::Err(_)=>{ info!("could not find bootrom... booting directly to rom"); - GameBoy::new(&mut mbc, joypad_provider, audio_devices) + GameBoy::new(&mut mbc, joypad_provider, audio_devices, sdl_gfx_device) } }; @@ -153,18 +116,7 @@ fn main() { } } - let frame_buffer = gameboy.cycle_frame(); - let scaled_buffer = extend_vec(frame_buffer, screen_scale as usize, SCREEN_WIDTH, SCREEN_HEIGHT); - - let mut pixels: *mut c_void = std::ptr::null_mut(); - let mut length: std::os::raw::c_int = 0; - SDL_LockTexture(texture, std::ptr::null(), &mut pixels, &mut length); - std::ptr::copy_nonoverlapping(scaled_buffer.as_ptr(),pixels as *mut u32, scaled_buffer.len()); - SDL_UnlockTexture(texture); - - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, std::ptr::null(), std::ptr::null()); - SDL_RenderPresent(renderer); + gameboy.cycle_frame(); let end = SDL_GetPerformanceCounter(); let elapsed_ms:f64 = (end - start) as f64 / SDL_GetPerformanceFrequency() as f64 * 1000.0; diff --git a/gb/src/mbc_handler.rs b/gb/src/mbc_handler.rs index 879dbfa7..c7edc901 100644 --- a/gb/src/mbc_handler.rs +++ b/gb/src/mbc_handler.rs @@ -10,7 +10,8 @@ pub const SAVE_SUFFIX:&str = ".sav"; pub fn initialize_mbc(program_name:&String)->Box{ let program_path = format!("{}{}",program_name,PROGRAM_SUFFIX); - let program = fs::read(program_path).expect("No program found, notice that function must have a `.gb` suffix"); + let error_message = format!("No program found, notice that the file must have a `.gb` suffix - {}\n", program_name); + let program = fs::read(program_path).expect(error_message.as_str()); let mbc_type = program[CARTRIDGE_TYPE_ADDRESS]; diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs index 36a645d6..2346a9f4 100644 --- a/gb/src/sdl_audio_device.rs +++ b/gb/src/sdl_audio_device.rs @@ -33,6 +33,7 @@ impl SdlAudioDevie{ let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); let device_id = unsafe{ + SDL_Init(SDL_INIT_AUDIO); SDL_ClearError(); let id = SDL_OpenAudioDevice(std::ptr::null(), 0, &desired_audio_spec, uninit_audio_spec.as_mut_ptr() , 0); diff --git a/gb/src/sdl_gfx_device.rs b/gb/src/sdl_gfx_device.rs new file mode 100644 index 00000000..d74ffa46 --- /dev/null +++ b/gb/src/sdl_gfx_device.rs @@ -0,0 +1,78 @@ +use std::ffi::{CString, c_void}; + +use lib_gb::ppu::gfx_device::GfxDevice; +use sdl2::sys::*; + +pub struct SdlGfxDevice{ + _window_name: CString, + renderer: *mut SDL_Renderer, + texture: *mut SDL_Texture, + width:u32, + height:u32, + sacle:u8, +} + +impl SdlGfxDevice{ + pub fn new(buffer_width:u32, buffer_height:u32, window_name:&str, screen_scale: u8)->Self{ + let cs_wnd_name = CString::new(window_name).unwrap(); + + let (_window, renderer, texture): (*mut SDL_Window, *mut SDL_Renderer, *mut SDL_Texture) = unsafe{ + SDL_Init(SDL_INIT_VIDEO); + let wind:*mut SDL_Window = SDL_CreateWindow( + cs_wnd_name.as_ptr(), + SDL_WINDOWPOS_UNDEFINED_MASK as i32, SDL_WINDOWPOS_UNDEFINED_MASK as i32, + buffer_width as i32 * 4, buffer_height as i32 * 4, 0); + + let rend: *mut SDL_Renderer = SDL_CreateRenderer(wind, -1, 0); + + let tex: *mut SDL_Texture = SDL_CreateTexture(rend, + SDL_PixelFormatEnum::SDL_PIXELFORMAT_ARGB8888 as u32, SDL_TextureAccess::SDL_TEXTUREACCESS_STREAMING as i32, + buffer_width as i32 * 4, buffer_height as i32 * 4); + + (wind, rend, tex) + }; + + Self{ + _window_name: cs_wnd_name, + renderer, + texture, + height:buffer_height, + width:buffer_width, + sacle:screen_scale + } + } + + fn extend_vec(vec:&[u32], scale:usize, w:usize, h:usize)->Vec{ + let mut new_vec = vec![0;vec.len()*scale*scale]; + for y in 0..h{ + let sy = y*scale; + for x in 0..w{ + let sx = x*scale; + for i in 0..scale{ + for j in 0..scale{ + new_vec[(sy+i)*(w*scale)+sx+j] = vec[y*w+x]; + } + } + } + } + return new_vec; + } +} + +impl GfxDevice for SdlGfxDevice{ + fn swap_buffer(&self, buffer:&[u32]) { + unsafe{ + let extended_buffer = Self::extend_vec(buffer, self.sacle as usize, self.width as usize, self.height as usize); + + let mut pixels: *mut c_void = std::ptr::null_mut(); + let mut length: std::os::raw::c_int = 0; + SDL_LockTexture(self.texture, std::ptr::null(), &mut pixels, &mut length); + std::ptr::copy_nonoverlapping(extended_buffer.as_ptr(),pixels as *mut u32, extended_buffer.len()); + SDL_UnlockTexture(self.texture); + + //There is no need to call SDL_RenderClear since im replacing the whole buffer + SDL_RenderCopy(self.renderer, self.texture, std::ptr::null(), std::ptr::null()); + SDL_RenderPresent(self.renderer); + } + } +} \ No newline at end of file diff --git a/lib_gb/src/lib.rs b/lib_gb/src/lib.rs index f32f9f39..40a90b86 100644 --- a/lib_gb/src/lib.rs +++ b/lib_gb/src/lib.rs @@ -5,6 +5,6 @@ pub mod mmu; pub mod keypad; pub mod apu; pub mod timer; +pub mod utils; -mod utils; pub use utils::GB_FREQUENCY; \ No newline at end of file diff --git a/lib_gb/src/machine/gameboy.rs b/lib_gb/src/machine/gameboy.rs index d730976c..5ed41351 100644 --- a/lib_gb/src/machine/gameboy.rs +++ b/lib_gb/src/machine/gameboy.rs @@ -1,36 +1,36 @@ use crate::{ - apu::{audio_device::AudioDevice, gb_apu::GbApu}, + apu::{audio_device::AudioDevice, gb_apu::GbApu}, cpu::gb_cpu::GbCpu, - keypad::{joypad::Joypad, joypad_provider::JoypadProvider, joypad_register_updater}, + keypad::{joypad::Joypad, joypad_provider::JoypadProvider, joypad_register_updater}, mmu::{carts::mbc::Mbc, gb_mmu::{GbMmu, BOOT_ROM_SIZE}, memory::Memory}, - ppu::{gb_ppu::{CYCLES_PER_FRAME, SCREEN_HEIGHT, SCREEN_WIDTH}} + ppu::gfx_device::GfxDevice }; use super::interrupts_handler::InterruptsHandler; use std::boxed::Box; use log::debug; +//CPU frequrncy: 4,194,304 / 59.727~ / 4 == 70224 / 4 +pub const CYCLES_PER_FRAME:u32 = 17556; -pub struct GameBoy<'a, JP: JoypadProvider, AD:AudioDevice> { +pub struct GameBoy<'a, JP: JoypadProvider, AD:AudioDevice, GFX:GfxDevice> { cpu: GbCpu, - mmu: GbMmu::<'a, AD>, + mmu: GbMmu::<'a, AD, GFX>, interrupts_handler:InterruptsHandler, - cycles_counter:u32, joypad_provider: JP } -impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{ +impl<'a, JP:JoypadProvider, AD:AudioDevice, GFX:GfxDevice> GameBoy<'a, JP, AD, GFX>{ - pub fn new_with_bootrom(mbc:&'a mut Box,joypad_provider:JP, audio_device:AD, boot_rom:[u8;BOOT_ROM_SIZE])->GameBoy{ + pub fn new_with_bootrom(mbc:&'a mut Box,joypad_provider:JP, audio_device:AD, gfx_device:GFX, boot_rom:[u8;BOOT_ROM_SIZE])->GameBoy{ GameBoy{ cpu:GbCpu::default(), - mmu:GbMmu::new_with_bootrom(mbc, boot_rom, GbApu::new(audio_device)), + mmu:GbMmu::new_with_bootrom(mbc, boot_rom, GbApu::new(audio_device), gfx_device), interrupts_handler: InterruptsHandler::default(), - cycles_counter:0, joypad_provider: joypad_provider } } - pub fn new(mbc:&'a mut Box,joypad_provider:JP, audio_device:AD)->GameBoy{ + pub fn new(mbc:&'a mut Box,joypad_provider:JP, audio_device:AD, gfx_device:GFX)->GameBoy{ let mut cpu = GbCpu::default(); //Values after the bootrom *cpu.af.value() = 0x190; @@ -42,19 +42,18 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{ GameBoy{ cpu:cpu, - mmu:GbMmu::new(mbc, GbApu::new(audio_device)), + mmu:GbMmu::new(mbc, GbApu::new(audio_device), gfx_device), interrupts_handler: InterruptsHandler::default(), - cycles_counter:0, joypad_provider: joypad_provider, } } - pub fn cycle_frame(&mut self)->&[u32;SCREEN_HEIGHT*SCREEN_WIDTH]{ + pub fn cycle_frame(&mut self){ let mut joypad = Joypad::default(); - let mut last_ppu_power_state:bool = self.mmu.io_components.ppu.screen_enable; + let mut cycles_counter = 0; - while self.cycles_counter < CYCLES_PER_FRAME{ + while cycles_counter < CYCLES_PER_FRAME{ self.joypad_provider.provide(&mut joypad); joypad_register_updater::update_joypad_registers(&joypad, &mut self.mmu); @@ -71,25 +70,9 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{ if interrupt_cycles != 0{ self.mmu.cycle(interrupt_cycles); } - - let iter_total_cycles= cpu_cycles_passed as u32 + interrupt_cycles as u32; - - - //In case the ppu just turned I want to keep it sync with the actual screen and thats why Im reseting the loop to finish - //the frame when the ppu finishes the frame - if !last_ppu_power_state && self.mmu.io_components.ppu.screen_enable{ - self.cycles_counter = 0; - } - self.cycles_counter += iter_total_cycles; - last_ppu_power_state = self.mmu.io_components.ppu.screen_enable; + cycles_counter += cpu_cycles_passed as u32 + interrupt_cycles as u32; } - - if self.cycles_counter >= CYCLES_PER_FRAME{ - self.cycles_counter -= CYCLES_PER_FRAME; - } - - return self.mmu.io_components.ppu.get_frame_buffer(); } fn execute_opcode(&mut self)->u8{ diff --git a/lib_gb/src/mmu/gb_mmu.rs b/lib_gb/src/mmu/gb_mmu.rs index 85356538..8c434add 100644 --- a/lib_gb/src/mmu/gb_mmu.rs +++ b/lib_gb/src/mmu/gb_mmu.rs @@ -1,5 +1,6 @@ use super::{io_components::IoComponents, memory::*}; use super::access_bus::AccessBus; +use crate::ppu::gfx_device::GfxDevice; use crate::{apu::{audio_device::AudioDevice, gb_apu::GbApu}, utils::memory_registers::BOOT_REGISTER_ADDRESS}; use super::carts::mbc::Mbc; use crate::ppu::ppu_state::PpuState; @@ -12,8 +13,8 @@ const DMA_DEST:u16 = 0xFE00; const BAD_READ_VALUE:u8 = 0xFF; -pub struct GbMmu<'a, D:AudioDevice>{ - pub io_components: IoComponents, +pub struct GbMmu<'a, D:AudioDevice, G:GfxDevice>{ + pub io_components: IoComponents, boot_rom:[u8;BOOT_ROM_SIZE], mbc: &'a mut Box, hram: [u8;HRAM_SIZE], @@ -22,7 +23,7 @@ pub struct GbMmu<'a, D:AudioDevice>{ //DMA only locks the used bus. there 2 possible used buses: extrnal (wram, rom, sram) and video (vram) -impl<'a, D:AudioDevice> Memory for GbMmu<'a, D>{ +impl<'a, D:AudioDevice, G:GfxDevice> Memory for GbMmu<'a, D, G>{ fn read(&self, address:u16)->u8{ if let Some (bus) = &self.io_components.dma.enable{ return match address{ @@ -45,7 +46,7 @@ impl<'a, D:AudioDevice> Memory for GbMmu<'a, D>{ }, 0xFE00..=0xFE9F=>{ if self.is_oam_ready_for_io(){ - return self.io_components.ppu.sprite_attribute_table[(address-0xFE00) as usize]; + return self.io_components.ppu.oam[(address-0xFE00) as usize]; } else{ log::warn!("bad oam read"); @@ -79,7 +80,7 @@ impl<'a, D:AudioDevice> Memory for GbMmu<'a, D>{ }, 0xFE00..=0xFE9F=>{ if self.is_oam_ready_for_io(){ - self.io_components.ppu.sprite_attribute_table[(address-0xFE00) as usize] = value; + self.io_components.ppu.oam[(address-0xFE00) as usize] = value; } else{ log::warn!("bad oam write") @@ -92,7 +93,7 @@ impl<'a, D:AudioDevice> Memory for GbMmu<'a, D>{ } } -impl<'a, D:AudioDevice> UnprotectedMemory for GbMmu<'a, D>{ +impl<'a, D:AudioDevice, G:GfxDevice> UnprotectedMemory for GbMmu<'a, D, G>{ fn read_unprotected(&self, address:u16) ->u8 { return match address{ 0x0..=0xFF=>{ @@ -109,7 +110,7 @@ impl<'a, D:AudioDevice> UnprotectedMemory for GbMmu<'a, D>{ 0xC000..=0xCFFF =>self.io_components.ram.read_bank0(address - 0xC000), 0xD000..=0xDFFF=>self.io_components.ram.read_current_bank(address-0xD000), 0xE000..=0xFDFF=>self.io_components.ram.read_bank0(address - 0xE000), - 0xFE00..=0xFE9F=>self.io_components.ppu.sprite_attribute_table[(address-0xFE00) as usize], + 0xFE00..=0xFE9F=>self.io_components.ppu.oam[(address-0xFE00) as usize], 0xFEA0..=0xFEFF=>0x0, 0xFF00..=0xFF7F=>self.io_components.read_unprotected(address - 0xFF00), 0xFF80..=0xFFFE=>self.hram[(address-0xFF80) as usize], @@ -125,7 +126,7 @@ impl<'a, D:AudioDevice> UnprotectedMemory for GbMmu<'a, D>{ 0xC000..=0xCFFF =>self.io_components.ram.write_bank0(address - 0xC000,value), 0xE000..=0xFDFF=>self.io_components.ram.write_bank0(address - 0xE000,value), 0xD000..=0xDFFF=>self.io_components.ram.write_current_bank(address-0xD000,value), - 0xFE00..=0xFE9F=>self.io_components.ppu.sprite_attribute_table[(address-0xFE00) as usize] = value, + 0xFE00..=0xFE9F=>self.io_components.ppu.oam[(address-0xFE00) as usize] = value, 0xFEA0..=0xFEFF=>{}, 0xFF00..=0xFF7F=>self.io_components.write_unprotected(address - 0xFF00, value), 0xFF80..=0xFFFE=>self.hram[(address-0xFF80) as usize] = value, @@ -134,10 +135,10 @@ impl<'a, D:AudioDevice> UnprotectedMemory for GbMmu<'a, D>{ } } -impl<'a, D:AudioDevice> GbMmu<'a, D>{ - pub fn new_with_bootrom(mbc:&'a mut Box, boot_rom:[u8;BOOT_ROM_SIZE], apu:GbApu)->Self{ +impl<'a, D:AudioDevice, G:GfxDevice> GbMmu<'a, D, G>{ + pub fn new_with_bootrom(mbc:&'a mut Box, boot_rom:[u8;BOOT_ROM_SIZE], apu:GbApu, gfx_device:G)->Self{ GbMmu{ - io_components:IoComponents::new(apu), + io_components:IoComponents::new(apu, gfx_device), mbc:mbc, hram:[0;HRAM_SIZE], interupt_enable_register:0, @@ -145,9 +146,9 @@ impl<'a, D:AudioDevice> GbMmu<'a, D>{ } } - pub fn new(mbc:&'a mut Box, apu:GbApu)->Self{ + pub fn new(mbc:&'a mut Box, apu:GbApu, gfx_device: G)->Self{ let mut mmu = GbMmu{ - io_components:IoComponents::new(apu), + io_components:IoComponents::new(apu, gfx_device), mbc:mbc, hram:[0;HRAM_SIZE], interupt_enable_register:0, diff --git a/lib_gb/src/mmu/io_components.rs b/lib_gb/src/mmu/io_components.rs index 7a3449d9..25a3068e 100644 --- a/lib_gb/src/mmu/io_components.rs +++ b/lib_gb/src/mmu/io_components.rs @@ -1,6 +1,8 @@ -use crate::{apu::{audio_device::AudioDevice, gb_apu::GbApu, set_nr11, set_nr12, set_nr13}, ppu::ppu_register_updater::*, timer::timer_register_updater::*, utils::memory_registers::*}; -use crate::ppu::gb_ppu::GbPpu; -use crate::apu::*; +use crate::{apu::{*,audio_device::AudioDevice, gb_apu::GbApu}, + ppu::{gb_ppu::GbPpu, ppu_register_updater::*, gfx_device::GfxDevice}, + timer::timer_register_updater::*, + utils::memory_registers::* +}; use crate::timer::gb_timer::GbTimer; use super::{access_bus::AccessBus, memory::*, oam_dma_transfer::OamDmaTransfer, ram::Ram}; use super::io_ports::*; @@ -9,11 +11,11 @@ use super::io_ports::*; pub const IO_PORTS_SIZE:usize = 0x80; -pub struct IoComponents{ +pub struct IoComponents{ pub ram: Ram, pub apu: GbApu, pub timer: GbTimer, - pub ppu:GbPpu, + pub ppu:GbPpu, ports:[u8;IO_PORTS_SIZE], pub dma:OamDmaTransfer, pub finished_boot:bool, @@ -35,7 +37,7 @@ io_port_index!(OBP1_REGISTER_INDEX, OBP1_REGISTER_ADDRESS); io_port_index!(IF_REGISTER_INDEX, IF_REGISTER_ADDRESS); -impl Memory for IoComponents{ +impl Memory for IoComponents{ fn read(&self, address:u16)->u8 { let mut value = self.ports[address as usize]; return match address { @@ -148,7 +150,7 @@ impl Memory for IoComponents{ } } -impl UnprotectedMemory for IoComponents{ +impl UnprotectedMemory for IoComponents{ fn read_unprotected(&self, address:u16)->u8 { self.ports[address as usize] } @@ -158,16 +160,16 @@ impl UnprotectedMemory for IoComponents{ } } -impl IoComponents{ - pub fn new(apu:GbApu)->Self{ - Self{apu, ports:[0;IO_PORTS_SIZE], timer:GbTimer::default(), ppu:GbPpu::default(), dma:OamDmaTransfer::default(),finished_boot:false, ram:Ram::default()} +impl IoComponents{ + pub fn new(apu:GbApu, gfx_device:GFX)->Self{ + Self{apu, ports:[0;IO_PORTS_SIZE], timer:GbTimer::default(), ppu:GbPpu::new(gfx_device), dma:OamDmaTransfer::default(),finished_boot:false, ram:Ram::default()} } pub fn cycle(&mut self, cycles:u32){ let mut if_register = self.ports[IF_REGISTER_INDEX as usize]; self.timer.cycle(&mut if_register, cycles as u8); self.apu.cycle(cycles as u8); - self.ppu.update_gb_screen(&mut if_register, cycles); + self.ppu.cycle( cycles, &mut if_register); self.ports[IF_REGISTER_INDEX as usize] = if_register; } } \ No newline at end of file diff --git a/lib_gb/src/ppu/color.rs b/lib_gb/src/ppu/color.rs index a0806109..26c8fb8c 100644 --- a/lib_gb/src/ppu/color.rs +++ b/lib_gb/src/ppu/color.rs @@ -33,3 +33,9 @@ impl PartialEq for Color{ self.r == color.r } } + +impl From for u32{ + fn from(color: Color) -> Self { + ((color.r as u32) << 16) | ((color.g as u32) << 8) | (color.b as u32) + } +} \ No newline at end of file diff --git a/lib_gb/src/ppu/extended_sprite.rs b/lib_gb/src/ppu/extended_sprite.rs deleted file mode 100644 index 09a92d96..00000000 --- a/lib_gb/src/ppu/extended_sprite.rs +++ /dev/null @@ -1,61 +0,0 @@ -use super::sprite::*; - -pub struct ExtendedSprite{ - pub pixels:[u8;128] -} - -impl ExtendedSprite { - const SIZE:u8 = 16; - - pub fn new() -> ExtendedSprite { - ExtendedSprite { pixels: [0; 128] } - } -} - -impl Clone for ExtendedSprite{ - fn clone(&self)->Self{ - ExtendedSprite{ - pixels:self.pixels - } - } -} - -impl Sprite for ExtendedSprite{ - fn size(&self)->u8 {Self::SIZE} - - fn get_pixel(&self, pos: u8)->u8{ - self.pixels[pos as usize] - } - - fn set_pixel(&mut self, pos: u8, pixel: u8){ - self.pixels[pos as usize] = pixel; - } - - fn flip_x(&mut self){ - let mut fliiped = ExtendedSprite::new(); - - for y in 0..16{ - let line = &self.pixels[y*8 .. (y+1)*8]; - for x in 0..4{ - fliiped.pixels[y*8 + x] = line[7-x]; - fliiped.pixels[y*8 + (7-x)] = line[x]; - } - } - - *self = fliiped; - } - - fn flip_y(&mut self){ - let mut flipped = ExtendedSprite::new(); - for y in 0..8{ - let upper_line = &self.pixels[y*8..(y+1)*8]; - let opposite_index = 15-y; - let lower_line = &self.pixels[opposite_index*8..(opposite_index+1)*8]; - - copy_pixels(&mut flipped,y as u8, lower_line); - copy_pixels(&mut flipped,opposite_index as u8, upper_line); - } - - *self = flipped; - } -} \ No newline at end of file diff --git a/lib_gb/src/ppu/fifo/background_fetcher.rs b/lib_gb/src/ppu/fifo/background_fetcher.rs new file mode 100644 index 00000000..42e633c6 --- /dev/null +++ b/lib_gb/src/ppu/fifo/background_fetcher.rs @@ -0,0 +1,127 @@ +use crate::{mmu::vram::VRam, utils::{bit_masks::*, fixed_size_queue::FixedSizeQueue, vec2::Vec2}}; +use super::{FIFO_SIZE, SPRITE_WIDTH, fetcher_state_machine::FetcherStateMachine, fetching_state::*}; + +pub struct BackgroundFetcher{ + pub fifo:FixedSizeQueue, + pub window_line_counter:u8, + pub has_wy_reached_ly:bool, + + current_x_pos:u8, + rendering_window:bool, + fetcher_state_machine:FetcherStateMachine, +} + +impl BackgroundFetcher{ + pub fn new()->Self{ + let state_machine = [FetchingState::Sleep, FetchingState::FetchTileNumber, FetchingState::Sleep, FetchingState::FetchLowTile, FetchingState::Sleep, FetchingState::FetchHighTile, FetchingState::Sleep, FetchingState::Push]; + BackgroundFetcher{ + fetcher_state_machine:FetcherStateMachine::new(state_machine), + current_x_pos:0, + fifo:FixedSizeQueue::::new(), + window_line_counter:0, + rendering_window:false, + has_wy_reached_ly:false, + } + } + + pub fn reset(&mut self){ + self.fifo.clear(); + self.current_x_pos = 0; + self.fetcher_state_machine.reset(); + self.rendering_window = false; + } + + pub fn pause(&mut self){ + self.fetcher_state_machine.reset(); + } + + pub fn try_increment_window_counter(&mut self, ly_register:u8, wy_register:u8){ + if self.rendering_window && ly_register >= wy_register{ + self.window_line_counter += 1; + } + } + + pub fn fetch_pixels(&mut self, vram:&VRam, lcd_control:u8, ly_register:u8, window_pos:&Vec2, bg_pos:&Vec2){ + self.has_wy_reached_ly = self.has_wy_reached_ly || ly_register == window_pos.y; + let last_rendering_status = self.rendering_window; + self.rendering_window = self.is_rendering_wnd(lcd_control, window_pos); + + // In case I was rendering a background pixel need to reset the state of the fetcher + // (and maybe clear the fifo but right now Im not doing it since im not sure what about the current_x_pos var) + if self.rendering_window && !last_rendering_status{ + self.fetcher_state_machine.reset(); + } + + match self.fetcher_state_machine.current_state(){ + FetchingState::FetchTileNumber=>{ + let tile_num = if self.rendering_window{ + let tile_map_address:u16 = if (lcd_control & BIT_6_MASK) == 0 {0x1800} else {0x1C00}; + vram.read_current_bank(tile_map_address + (32 * (self.window_line_counter as u16 / SPRITE_WIDTH as u16)) + ((self.current_x_pos - window_pos.x) as u16 / SPRITE_WIDTH as u16)) + } + else{ + let tile_map_address = if (lcd_control & BIT_3_MASK) == 0 {0x1800} else {0x1C00}; + let scx_offset = ((bg_pos.x as u16 + self.current_x_pos as u16) / SPRITE_WIDTH as u16 ) & 31; + let scy_offset = ((bg_pos.y as u16 + ly_register as u16) & 0xFF) / SPRITE_WIDTH as u16; + + vram.read_current_bank(tile_map_address + ((32 * scy_offset) + scx_offset)) + }; + + self.fetcher_state_machine.data.reset(); + self.fetcher_state_machine.data.tile_data = Some(tile_num); + } + FetchingState::FetchLowTile=>{ + let tile_num = self.fetcher_state_machine.data.tile_data.expect("State machine is corrupted, No Tile data on FetchLowTIle"); + let address = self.get_tila_data_address(lcd_control, bg_pos, ly_register, tile_num); + let low_data = vram.read_current_bank(address); + + self.fetcher_state_machine.data.low_tile_data = Some(low_data); + } + FetchingState::FetchHighTile=>{ + let tile_num= self.fetcher_state_machine.data.tile_data.expect("State machine is corrupted, No Tile data on FetchHighTIle"); + let address = self.get_tila_data_address(lcd_control, bg_pos, ly_register, tile_num); + let high_data = vram.read_current_bank(address + 1); + + self.fetcher_state_machine.data.high_tile_data = Some(high_data); + } + FetchingState::Push=>{ + let low_data = self.fetcher_state_machine.data.low_tile_data.expect("State machine is corrupted, No Low data on Push"); + let high_data = self.fetcher_state_machine.data.high_tile_data.expect("State machine is corrupted, No High data on Push"); + if self.fifo.len() == 0{ + if lcd_control & BIT_0_MASK == 0{ + for _ in 0..SPRITE_WIDTH{ + //When the baclkground is off pushes 0 + self.fifo.push(0); + self.current_x_pos += 1; + } + } + else{ + for i in (0..SPRITE_WIDTH).rev(){ + let mask = 1 << i; + let mut pixel = (low_data & mask) >> i; + pixel |= ((high_data & mask) >> i) << 1; + self.fifo.push(pixel); + self.current_x_pos += 1; + } + } + } + } + FetchingState::Sleep=>{} + } + + self.fetcher_state_machine.advance(); + } + + fn get_tila_data_address(&self, lcd_control:u8, bg_pos:&Vec2, ly_register:u8, tile_num:u8)->u16{ + let current_tile_base_data_address = if (lcd_control & BIT_4_MASK) == 0 && (tile_num & BIT_7_MASK) == 0 {0x1000} else {0}; + let current_tile_data_address = current_tile_base_data_address + (tile_num as u16 * 16); + return if self.rendering_window{ + current_tile_data_address + (2 * (self.window_line_counter % SPRITE_WIDTH)) as u16 + } else{ + current_tile_data_address + (2 * ((bg_pos.y as u16 + ly_register as u16) % SPRITE_WIDTH as u16)) + }; + } + + fn is_rendering_wnd(&self, lcd_control:u8, window_pos:&Vec2)->bool{ + window_pos.x <= self.current_x_pos && self.has_wy_reached_ly && (lcd_control & BIT_5_MASK) != 0 + } +} \ No newline at end of file diff --git a/lib_gb/src/ppu/fifo/fetcher_state_machine.rs b/lib_gb/src/ppu/fifo/fetcher_state_machine.rs new file mode 100644 index 00000000..e051c30b --- /dev/null +++ b/lib_gb/src/ppu/fifo/fetcher_state_machine.rs @@ -0,0 +1,30 @@ +use super::fetching_state::*; + +pub struct FetcherStateMachine{ + pub data:FetchingStateData, + state:usize, + state_machine:[FetchingState;8] +} + +impl FetcherStateMachine{ + pub fn advance(&mut self){ + self.state = (self.state + 1) % 8; + } + + pub fn new(state_machine:[FetchingState;8])->Self{ + Self{ + data:FetchingStateData{high_tile_data:None, low_tile_data:None, tile_data:None}, + state:0, + state_machine + } + } + + pub fn reset(&mut self){ + self.state = 0; + self.data.reset(); + } + + pub fn current_state(&self)->&FetchingState{ + &self.state_machine[self.state] + } +} \ No newline at end of file diff --git a/lib_gb/src/ppu/fifo/fetching_state.rs b/lib_gb/src/ppu/fifo/fetching_state.rs new file mode 100644 index 00000000..68b6c777 --- /dev/null +++ b/lib_gb/src/ppu/fifo/fetching_state.rs @@ -0,0 +1,21 @@ +pub enum FetchingState{ + FetchTileNumber, + FetchLowTile, + FetchHighTile, + Push, + Sleep +} + +pub struct FetchingStateData{ + pub tile_data:Option, + pub low_tile_data:Option, + pub high_tile_data:Option, +} + +impl FetchingStateData{ + pub fn reset(&mut self){ + self.high_tile_data = None; + self.low_tile_data = None; + self.tile_data = None; + } +} \ No newline at end of file diff --git a/lib_gb/src/ppu/fifo/mod.rs b/lib_gb/src/ppu/fifo/mod.rs new file mode 100644 index 00000000..cd122215 --- /dev/null +++ b/lib_gb/src/ppu/fifo/mod.rs @@ -0,0 +1,7 @@ +pub mod background_fetcher; +pub mod sprite_fetcher; +mod fetching_state; +mod fetcher_state_machine; + +pub const FIFO_SIZE:usize = 8; +pub const SPRITE_WIDTH:u8 = 8; \ No newline at end of file diff --git a/lib_gb/src/ppu/fifo/sprite_fetcher.rs b/lib_gb/src/ppu/fifo/sprite_fetcher.rs new file mode 100644 index 00000000..fe6a3625 --- /dev/null +++ b/lib_gb/src/ppu/fifo/sprite_fetcher.rs @@ -0,0 +1,140 @@ +use crate::{mmu::vram::VRam, ppu::sprite_attribute::SpriteAttribute, utils::{self, bit_masks::{BIT_0_MASK, BIT_2_MASK}, fixed_size_queue::FixedSizeQueue}}; +use super::{FIFO_SIZE, SPRITE_WIDTH, fetcher_state_machine::FetcherStateMachine, fetching_state::*}; + +pub const NORMAL_SPRITE_HIGHT:u8 = 8; +pub const EXTENDED_SPRITE_HIGHT:u8 = 16; +pub const MAX_SPRITES_PER_LINE:usize = 10; + +pub struct SpriteFetcher{ + pub fifo:FixedSizeQueue<(u8, u8), FIFO_SIZE>, + pub oam_entries:[SpriteAttribute; 10], + pub oam_entries_len:u8, + pub rendering:bool, + + fetcher_state_machine:FetcherStateMachine, + current_oam_entry:u8, +} + +impl SpriteFetcher{ + pub fn new()->Self{ + let oam_entries:[SpriteAttribute; MAX_SPRITES_PER_LINE] = utils::create_array(|| SpriteAttribute::new(0,0,0,0)); + let state_machine:[FetchingState;8] = [FetchingState::FetchTileNumber, FetchingState::FetchTileNumber, FetchingState::Sleep, FetchingState::FetchLowTile, FetchingState::Sleep, FetchingState::FetchHighTile, FetchingState::Sleep, FetchingState::Push]; + + SpriteFetcher{ + fetcher_state_machine:FetcherStateMachine::new(state_machine), + current_oam_entry:0, + oam_entries_len:0, + oam_entries, + fifo:FixedSizeQueue::<(u8,u8), 8>::new(), + rendering:false, + } + } + + pub fn reset(&mut self){ + self.current_oam_entry = 0; + self.oam_entries_len = 0; + self.fetcher_state_machine.reset(); + self.fifo.clear(); + self.rendering = false; + } + + pub fn fetch_pixels(&mut self, vram:&VRam, lcd_control:u8, ly_register:u8, current_x_pos:u8){ + let sprite_size = if lcd_control & BIT_2_MASK == 0 {NORMAL_SPRITE_HIGHT} else{EXTENDED_SPRITE_HIGHT}; + + match self.fetcher_state_machine.current_state(){ + FetchingState::FetchTileNumber=>{ + self.try_fetch_tile_number(current_x_pos, lcd_control); + } + FetchingState::FetchLowTile=>{ + let tile_num = self.fetcher_state_machine.data.tile_data.expect("State machine is corrupted, No Tile data on FetchLowTIle"); + let oam_attribute = &self.oam_entries[self.current_oam_entry as usize]; + let current_tile_data_address = Self::get_current_tile_data_address(ly_register, oam_attribute, sprite_size, tile_num); + let low_data = vram.read_current_bank(current_tile_data_address); + self.fetcher_state_machine.data.low_tile_data = Some(low_data); + self.fetcher_state_machine.advance(); + } + FetchingState::FetchHighTile=>{ + let tile_num= self.fetcher_state_machine.data.tile_data.expect("State machine is corrupted, No Tile data on FetchHighTIle"); + let oam_attribute = &self.oam_entries[self.current_oam_entry as usize]; + let current_tile_data_address = Self::get_current_tile_data_address(ly_register, oam_attribute, sprite_size, tile_num); + let high_data = vram.read_current_bank(current_tile_data_address + 1); + self.fetcher_state_machine.data.high_tile_data = Some(high_data); + self.fetcher_state_machine.advance(); + } + FetchingState::Push=>{ + let low_data = self.fetcher_state_machine.data.low_tile_data.expect("State machine is corrupted, No Low data on Push"); + let high_data = self.fetcher_state_machine.data.high_tile_data.expect("State machine is corrupted, No High data on Push"); + let oam_attribute = &self.oam_entries[self.current_oam_entry as usize]; + let start_x = self.fifo.len(); + let skip_x = 8 - (oam_attribute.x - current_x_pos) as usize; + + if oam_attribute.flip_x{ + for i in (0 + skip_x)..SPRITE_WIDTH as usize{ + let pixel = Self::get_decoded_pixel(i, low_data, high_data); + if i + skip_x >= start_x { + self.fifo.push((pixel, self.current_oam_entry)); + } + else if self.fifo[i + skip_x].0 == 0{ + self.fifo[i+ skip_x] = (pixel, self.current_oam_entry); + } + } + } + else{ + let fifo_max_index = FIFO_SIZE as usize - 1; + for i in (0..(SPRITE_WIDTH as usize - skip_x)).rev(){ + let pixel = Self::get_decoded_pixel(i, low_data, high_data); + if fifo_max_index - skip_x - i >= start_x { + self.fifo.push((pixel, self.current_oam_entry)); + } + else if self.fifo[fifo_max_index - skip_x - i].0 == 0{ + self.fifo[fifo_max_index - skip_x - i] = (pixel, self.current_oam_entry); + } + } + } + + self.current_oam_entry += 1; + self.fetcher_state_machine.advance(); + } + FetchingState::Sleep=>self.fetcher_state_machine.advance() + } + } + + //This is a function on order to abort if rendering + fn try_fetch_tile_number(&mut self, current_x_pos: u8, lcd_control: u8) { + if self.oam_entries_len > self.current_oam_entry{ + let oam_entry = &self.oam_entries[self.current_oam_entry as usize]; + if oam_entry.x <= current_x_pos + SPRITE_WIDTH && current_x_pos < oam_entry.x{ + let mut tile_number = oam_entry.tile_number; + if lcd_control & BIT_2_MASK != 0{ + tile_number &= !BIT_0_MASK + } + self.rendering = true; + self.fetcher_state_machine.data.reset(); + self.fetcher_state_machine.data.tile_data = Some(tile_number); + self.fetcher_state_machine.advance(); + return; + } + } + self.rendering = false; + } + + // Receiving the tile_num since in case of extended sprite this could change (the first bit is reset) + fn get_current_tile_data_address(ly_register:u8, sprite_attrib:&SpriteAttribute, sprite_size:u8, tile_num:u8)->u16{ + return if sprite_attrib.flip_y{ + // Since Im flipping but dont know for what rect (8X8 or 8X16) I need sub this from the size (minus 1 casue im starting to count from 0 in the screen lines). + tile_num as u16 * 16 + (2 * (sprite_size - 1 - (16 - (sprite_attrib.y - ly_register)))) as u16 + } + else{ + // Since the sprite attribute y pos is the right most dot of the rect + // Im subtracting this from 16 (since the rects are 8X16) + tile_num as u16 * 16 + (2 * (16 - (sprite_attrib.y - ly_register))) as u16 + }; + } + + fn get_decoded_pixel(index: usize, low_data: u8, high_data: u8) -> u8 { + let mask = 1 << index; + let mut pixel = (low_data & mask) >> index; + pixel |= ((high_data & mask) >> index) << 1; + pixel + } +} \ No newline at end of file diff --git a/lib_gb/src/ppu/gb_ppu.rs b/lib_gb/src/ppu/gb_ppu.rs index f108a644..06758648 100644 --- a/lib_gb/src/ppu/gb_ppu.rs +++ b/lib_gb/src/ppu/gb_ppu.rs @@ -1,64 +1,35 @@ +use crate::utils::{vec2::Vec2, bit_masks::*}; use crate::mmu::vram::VRam; -use super::ppu_state::PpuState; -use super::color::Color; -use super::colors::*; -use crate::utils::vec2::Vec2; -use super::colors::WHITE; -use super::normal_sprite::NormalSprite; -use super::extended_sprite::ExtendedSprite; -use super::sprite::Sprite; -use super::sprite_attribute::SpriteAttribute; -use crate::utils::{ - bit_masks::* -}; -use std::cmp; +use crate::ppu::color::*; +use crate::ppu::colors::*; +use crate::ppu::gfx_device::GfxDevice; +use crate::ppu::{ppu_state::PpuState, sprite_attribute::SpriteAttribute}; + +use super::fifo::background_fetcher::BackgroundFetcher; +use super::fifo::{FIFO_SIZE, sprite_fetcher::*}; pub const SCREEN_HEIGHT: usize = 144; pub const SCREEN_WIDTH: usize = 160; +const OAM_ENTRY_SIZE:u16 = 4; +const OAM_MEMORY_SIZE:usize = 0xA0; -//CPU frequrncy: 4,194,304 / 59.727~ / 4 == 70224 / 4 -pub const CYCLES_PER_FRAME:u32 = 17556; - -const OAM_CLOCKS:u8 = 20; -const PIXEL_TRANSFER_CLOCKS:u8 = 43; -const H_BLANK_CLOCKS:u8 = 51; -const DRAWING_CYCLE_CLOCKS: u8 = OAM_CLOCKS + H_BLANK_CLOCKS + PIXEL_TRANSFER_CLOCKS; -const LY_MAX_VALUE:u8 = 153; -const SPRITE_ATTRIBUTE_TABLE_SIZE:usize = 0xA0; -const OAM_SIZE:u16 = 0xA0; -const OBJ_PER_LINE:usize = 10; -const SPRITE_WIDTH:u8 = 8; -const NORMAL_SPRITE_HIEGHT:u8 = 8; -const SPRITE_MAX_HEIGHT:u8 = 16; -const BG_SPRITES_PER_LINE:u16 = 32; -const SPRITE_SIZE_IN_MEMORY:u16 = 16; - -const BLANK_SCREEN_BUFFER:[u32; SCREEN_HEIGHT * SCREEN_WIDTH] = [GbPpu::color_as_uint(&WHITE);SCREEN_HEIGHT * SCREEN_WIDTH]; +const OAM_SEARCH_T_CYCLES_LENGTH: u16 = 80; +const HBLANK_T_CYCLES_LENGTH: u16 = 456; +const VBLANK_T_CYCLES_LENGTH: u16 = 4560; -pub struct GbPpu { +pub struct GbPpu{ pub vram: VRam, - pub sprite_attribute_table:[u8;SPRITE_ATTRIBUTE_TABLE_SIZE], - - pub screen_buffer: [u32; SCREEN_HEIGHT*SCREEN_WIDTH], - pub screen_enable: bool, - pub window_enable: bool, - pub sprite_extended: bool, - pub background_enabled: bool, - pub gbc_mode: bool, - pub sprite_enable: bool, - pub window_tile_map_address: bool, - pub window_tile_background_map_data_address: bool, - pub background_tile_map_address: bool, - pub background_scroll: Vec2, - pub window_scroll: Vec2, - pub bg_color_mapping: [Color; 4], - pub obj_color_mapping0: [Option;4], - pub obj_color_mapping1: [Option;4], - pub current_line_drawn: u8, + pub oam:[u8;OAM_MEMORY_SIZE], pub state:PpuState, - + pub lcd_control:u8, pub stat_register:u8, pub lyc_register:u8, + pub ly_register:u8, + pub window_pos:Vec2, + pub bg_pos:Vec2, + pub bg_color_mapping: [Color; 4], + pub obj_color_mapping0: [Option;4], + pub obj_color_mapping1: [Option;4], //interrupts pub v_blank_interrupt_request:bool, @@ -66,448 +37,264 @@ pub struct GbPpu { pub oam_search_interrupt_request:bool, pub coincidence_interrupt_request:bool, - window_active:bool, - window_line_counter:u8, - line_rendered:bool, - current_cycle:u32, - last_screen_state:bool, - v_blank_triggered:bool, - stat_triggered:bool + gfx_device: GFX, + t_cycles_passed:u16, + screen_buffer: [u32; SCREEN_HEIGHT * SCREEN_WIDTH], + push_lcd_buffer:Vec, + screen_buffer_index:usize, + pixel_x_pos:u8, + scanline_started:bool, + bg_fetcher:BackgroundFetcher, + sprite_fetcher:SpriteFetcher, + stat_triggered:bool, + trigger_stat_interrupt:bool, } -impl Default for GbPpu { - fn default() -> Self { - GbPpu { - vram:VRam::default(), - sprite_attribute_table: [0;SPRITE_ATTRIBUTE_TABLE_SIZE], - stat_register:0, - lyc_register:0, - background_enabled: false, - background_scroll: Vec2:: { x: 0, y: 0 }, - window_scroll: Vec2:: { x: 0, y: 0 }, - background_tile_map_address: false, - gbc_mode: false, - screen_buffer: [0; SCREEN_HEIGHT*SCREEN_WIDTH], - screen_enable: false, - sprite_enable: false, - sprite_extended: false, - window_enable: false, - window_tile_background_map_data_address: false, - window_tile_map_address: false, - bg_color_mapping: [WHITE, LIGHT_GRAY, DARK_GRAY, BLACK], +impl GbPpu{ + pub fn new(device:GFX) -> Self { + Self{ + gfx_device: device, + vram: VRam::default(), + oam: [0;OAM_MEMORY_SIZE], + stat_register: 0, + lyc_register: 0, + lcd_control: 0, + bg_pos: Vec2::{x:0, y:0}, + window_pos: Vec2::{x:0,y:0}, + screen_buffer:[0;SCREEN_HEIGHT * SCREEN_WIDTH], + bg_color_mapping:[WHITE, LIGHT_GRAY, DARK_GRAY, BLACK], obj_color_mapping0: [None, Some(LIGHT_GRAY), Some(DARK_GRAY), Some(BLACK)], obj_color_mapping1: [None, Some(LIGHT_GRAY), Some(DARK_GRAY), Some(BLACK)], - current_line_drawn:0, - state:PpuState::OamSearch, - line_rendered:false, - window_line_counter:0, - window_active:false, - current_cycle:0, - last_screen_state:true, - v_blank_triggered:false, - stat_triggered:false, + ly_register:0, + state: PpuState::OamSearch, //interrupts - v_blank_interrupt_request:false, + v_blank_interrupt_request:false, h_blank_interrupt_request:false, - oam_search_interrupt_request:false, - coincidence_interrupt_request:false + oam_search_interrupt_request:false, + coincidence_interrupt_request:false, + screen_buffer_index:0, + t_cycles_passed:0, + stat_triggered:false, + trigger_stat_interrupt:false, + bg_fetcher:BackgroundFetcher::new(), + sprite_fetcher:SpriteFetcher::new(), + push_lcd_buffer:Vec::::new(), + pixel_x_pos:0, + scanline_started:false } } -} -impl GbPpu { - const fn color_as_uint(color: &Color) -> u32 { - ((color.r as u32) << 16) | ((color.g as u32) << 8) | (color.b as u32) + pub fn turn_off(&mut self){ + self.screen_buffer_index = 0; + self.t_cycles_passed = 0; + //This is an expensive operation! + unsafe{std::ptr::write_bytes(self.screen_buffer.as_mut_ptr(), 0xFF, self.screen_buffer.len())}; + self.gfx_device.swap_buffer(&self.screen_buffer); + self.state = PpuState::Hblank; + self.ly_register = 0; + self.stat_triggered = false; + self.trigger_stat_interrupt = false; + self.bg_fetcher.has_wy_reached_ly = false; + self.bg_fetcher.window_line_counter = 0; + self.bg_fetcher.reset(); + self.sprite_fetcher.reset(); + self.pixel_x_pos = 0; } - pub fn get_frame_buffer(&self)->&[u32;SCREEN_HEIGHT*SCREEN_WIDTH]{ - return &self.screen_buffer; + pub fn turn_on(&mut self){ + self.state = PpuState::OamSearch; } - fn update_ly(&mut self){ - - let line = self.current_cycle/DRAWING_CYCLE_CLOCKS as u32; - if self.current_cycle >= CYCLES_PER_FRAME { - self.current_line_drawn = 0; - self.line_rendered = false; - self.current_cycle -= CYCLES_PER_FRAME; - self.window_line_counter = 0; - self.window_active = false; - } - else if self.current_line_drawn != line as u8{ - self.current_line_drawn = line as u8; - self.line_rendered = false; - } - else if self.current_line_drawn > LY_MAX_VALUE{ - std::panic!("invalid LY register value: {}", self.current_line_drawn); + pub fn cycle(&mut self, m_cycles:u32, if_register:&mut u8){ + if self.lcd_control & BIT_7_MASK == 0{ + return; } - } - fn update_ly_register(&mut self, if_register:&mut u8){ - if self.current_line_drawn >= SCREEN_HEIGHT as u8 && !self.v_blank_triggered{ - *if_register |= BIT_0_MASK; - - self.v_blank_triggered = true; - } - else if self.current_line_drawn < SCREEN_HEIGHT as u8{ - self.v_blank_triggered = false; + self.cycle_fetcher(m_cycles, if_register); + + self.update_stat_register(if_register); + + for pixel in self.push_lcd_buffer.iter(){ + self.screen_buffer[self.screen_buffer_index] = u32::from(*pixel); + self.screen_buffer_index += 1; + if self.screen_buffer_index == self.screen_buffer.len(){ + self.gfx_device.swap_buffer(&self.screen_buffer); + self.screen_buffer_index = 0; + } } - } - fn update_stat_register(&mut self, if_register:&mut u8){ - let mut lcd_stat_interrupt:bool = false; + self.push_lcd_buffer.clear(); + } - if self.current_line_drawn == self.lyc_register{ - self.stat_register |= BIT_2_MASK; + fn update_stat_register(&mut self, if_register: &mut u8) { + self.stat_register &= 0b1111_1100; + self.stat_register |= self.state as u8; + if self.ly_register == self.lyc_register{ if self.coincidence_interrupt_request { - lcd_stat_interrupt = true; + self.trigger_stat_interrupt = true; } + self.stat_register |= BIT_2_MASK; } else{ self.stat_register &= !BIT_2_MASK; } - - //clears the 2 lower bits - self.stat_register = (self.stat_register >> 2)<<2; - self.stat_register |= self.state as u8; - - match self.state{ - PpuState::OamSearch=>{ - if self.oam_search_interrupt_request{ - lcd_stat_interrupt = true; - } - }, - PpuState::Hblank=>{ - if self.h_blank_interrupt_request{ - lcd_stat_interrupt = true; - } - }, - PpuState::Vblank=>{ - if self.v_blank_interrupt_request{ - lcd_stat_interrupt = true; - } - }, - _=>{} - } - - if lcd_stat_interrupt{ + if self.trigger_stat_interrupt{ if !self.stat_triggered{ *if_register |= BIT_1_MASK; - self.stat_triggered = true; } } else{ self.stat_triggered = false; } + self.trigger_stat_interrupt = false; } - fn get_ppu_state(cycle_counter:u32, last_ly:u8)->PpuState{ - if last_ly >= SCREEN_HEIGHT as u8{ - return PpuState::Vblank; - } - - //getting the reminder of the clocks - let current_line_clocks = cycle_counter % DRAWING_CYCLE_CLOCKS as u32; - - const OAM_SERACH_END:u8 = OAM_CLOCKS - 1; - const PIXEL_TRANSFER_START:u8 = OAM_CLOCKS; - const PIXEL_TRANSFER_END:u8 = OAM_CLOCKS + PIXEL_TRANSFER_CLOCKS - 1; - const H_BLANK_START:u8 = OAM_CLOCKS + PIXEL_TRANSFER_CLOCKS; - const H_BLANK_END:u8 = H_BLANK_START + H_BLANK_CLOCKS - 1; - - return match current_line_clocks as u8{ - 0 ..= OAM_SERACH_END => PpuState::OamSearch, // 0-19 (20) - PIXEL_TRANSFER_START ..= PIXEL_TRANSFER_END => PpuState::PixelTransfer, //20-62 (43) - H_BLANK_START ..= H_BLANK_END => PpuState::Hblank,//63-113(51) - _=>std::panic!("Error calculating ppu state") - }; - } - - pub fn update_gb_screen(&mut self,if_register:&mut u8, cycles_passed:u32){ - if !self.screen_enable && self.last_screen_state { - self.current_line_drawn = 0; - self.current_cycle = 0; - self.screen_buffer = BLANK_SCREEN_BUFFER; - self.state = PpuState::Hblank; - self.window_active = false; - self.last_screen_state = self.screen_enable; - return; - } - else if !self.screen_enable{ - return; - } - - self.last_screen_state = self.screen_enable; - - self.current_cycle += cycles_passed as u32; - self.update_ly(); - self.state = Self::get_ppu_state(self.current_cycle, self.current_line_drawn); - - self.update_ly_register(if_register); - self.update_stat_register(if_register); - - if self.state as u8 == PpuState::PixelTransfer as u8{ - if !self.line_rendered { - self.line_rendered = true; - - let mut frame_buffer_line = self.get_bg_frame_buffer(); - self.draw_window_frame_buffer(&mut frame_buffer_line); - self.draw_objects_frame_buffer(&mut frame_buffer_line); + fn cycle_fetcher(&mut self, m_cycles:u32, if_register:&mut u8){ + let sprite_height = if (self.lcd_control & BIT_2_MASK) != 0 {EXTENDED_SPRITE_HIGHT} else {NORMAL_SPRITE_HIGHT}; - let line_index = self.current_line_drawn as usize * SCREEN_WIDTH; - - for i in line_index..line_index+SCREEN_WIDTH{ - self.screen_buffer[i] = Self::color_as_uint(&frame_buffer_line[(i - line_index)]); + for _ in 0..m_cycles * 2{ + match self.state{ + PpuState::OamSearch=>{ + let oam_index = self.t_cycles_passed / 2; + let oam_entry_address = (oam_index * OAM_ENTRY_SIZE) as usize; + let end_y = self.oam[oam_entry_address]; + let end_x = self.oam[oam_entry_address + 1]; + + if end_x > 0 && self.ly_register + 16 >= end_y && self.ly_register + 16 < end_y + sprite_height && self.sprite_fetcher.oam_entries_len < MAX_SPRITES_PER_LINE as u8{ + let tile_number = self.oam[oam_entry_address + 2]; + let attributes = self.oam[oam_entry_address + 3]; + self.sprite_fetcher.oam_entries[self.sprite_fetcher.oam_entries_len as usize] = SpriteAttribute::new(end_y, end_x, tile_number, attributes); + self.sprite_fetcher.oam_entries_len += 1; + } + + self.t_cycles_passed += 2; //half a m_cycle + + if self.t_cycles_passed == OAM_SEARCH_T_CYCLES_LENGTH{ + let slice = self.sprite_fetcher.oam_entries[0..self.sprite_fetcher.oam_entries_len as usize].as_mut(); + slice.sort_by(|s1:&SpriteAttribute, s2:&SpriteAttribute| s1.x.cmp(&s2.x)); + self.state = PpuState::PixelTransfer; + self.scanline_started = false; + } + } + PpuState::Hblank=>{ + self.t_cycles_passed += 2; + + if self.t_cycles_passed == HBLANK_T_CYCLES_LENGTH{ + self.pixel_x_pos = 0; + self.t_cycles_passed = 0; + self.ly_register += 1; + if self.ly_register == SCREEN_HEIGHT as u8{ + self.state = PpuState::Vblank; + //reseting the window counter on vblank + self.bg_fetcher.window_line_counter = 0; + self.bg_fetcher.has_wy_reached_ly = false; + *if_register |= BIT_0_MASK; + if self.v_blank_interrupt_request{ + self.trigger_stat_interrupt = true; + } + } + else{ + self.state = PpuState::OamSearch; + if self.oam_search_interrupt_request{ + self.trigger_stat_interrupt = true; + } + } + } + } + PpuState::Vblank=>{ + if self.t_cycles_passed == VBLANK_T_CYCLES_LENGTH{ + self.state = PpuState::OamSearch; + if self.oam_search_interrupt_request{ + self.trigger_stat_interrupt = true; + } + self.pixel_x_pos = 0; + self.t_cycles_passed = 0; + self.ly_register = 0; + } + else{ + //VBlank is technically 10 HBlank combined + self.ly_register = SCREEN_HEIGHT as u8 + (self.t_cycles_passed / HBLANK_T_CYCLES_LENGTH) as u8; + } + + self.t_cycles_passed += 2; + } + PpuState::PixelTransfer=>{ + for _ in 0..2{ + if self.pixel_x_pos < SCREEN_WIDTH as u8{ + if self.lcd_control & BIT_1_MASK != 0{ + self.sprite_fetcher.fetch_pixels(&self.vram, self.lcd_control, self.ly_register, self.pixel_x_pos); + } + if self.sprite_fetcher.rendering{ + self.bg_fetcher.pause(); + } + else{ + self.bg_fetcher.fetch_pixels(&self.vram, self.lcd_control, self.ly_register, &self.window_pos, &self.bg_pos); + self.try_push_to_lcd(); + if self.pixel_x_pos == SCREEN_WIDTH as u8{ + self.state = PpuState::Hblank; + if self.h_blank_interrupt_request{ + self.trigger_stat_interrupt = true; + } + self.bg_fetcher.try_increment_window_counter(self.ly_register, self.window_pos.y); + self.bg_fetcher.reset(); + self.sprite_fetcher.reset(); + + // If im on the first iteration and finished the 160 pixels break; + // In this case the number of t_cycles should be eneven but it will break + // my code way too much for now so Im leaving this as it is... (maybe in the future) + break; + } + } + } + } + self.t_cycles_passed += 2; } - } - } - } - - fn read_vram(&self, address:u16)->u8{ - self.vram.read_current_bank(address - 0x8000) - } - - fn get_bg_frame_buffer(&self)-> [Color;SCREEN_WIDTH] { - if !self.background_enabled{ - //color in BGP 0 - let color = self.get_bg_color(0); - return [color;SCREEN_WIDTH] - } - - let current_line = self.current_line_drawn; - - let address = if self.background_tile_map_address { - 0x9C00 - } else { - 0x9800 - }; - let mut line_sprites:Vec = Vec::with_capacity(BG_SPRITES_PER_LINE as usize); - let index = ((current_line.wrapping_add(self.background_scroll.y)) / NORMAL_SPRITE_HIEGHT) as u16; - if self.window_tile_background_map_data_address { - for i in 0..BG_SPRITES_PER_LINE { - let chr: u8 = self.read_vram(address + (index*BG_SPRITES_PER_LINE) + i); - let sprite = self.get_normal_sprite(chr, 0x8000); - line_sprites.push(sprite); - } - } - else { - for i in 0..BG_SPRITES_PER_LINE { - let mut chr: u8 = self.read_vram(address + (index*BG_SPRITES_PER_LINE) + i); - chr = chr.wrapping_add(0x80); - let sprite = self.get_normal_sprite(chr, 0x8800); - line_sprites.push(sprite); - } - } - - let mut drawn_line:[Color; 256] = [Color::default();256]; - - let sprite_line = (current_line as u16 + self.background_scroll.y as u16) % 8; - for i in 0..line_sprites.len(){ - for j in 0..SPRITE_WIDTH{ - let pixel = line_sprites[i].pixels[((sprite_line as u8 * SPRITE_WIDTH) + j) as usize]; - drawn_line[(i * SPRITE_WIDTH as usize) + j as usize] = self.get_bg_color(pixel); - } - } - - let mut screen_line:[Color;SCREEN_WIDTH] = [Color::default();SCREEN_WIDTH]; - for i in 0..SCREEN_WIDTH{ - let index:usize = (i as u8).wrapping_add(self.background_scroll.x) as usize; - screen_line[i] = drawn_line[index] - } - - return screen_line; - } - - fn get_normal_sprite(&self, index:u8, data_address:u16)->NormalSprite{ - let mut sprite = NormalSprite::new(); - - let mut line_number = 0; - let start:u16 = index as u16 * SPRITE_SIZE_IN_MEMORY; - let end:u16 = start + SPRITE_SIZE_IN_MEMORY; - for j in (start .. end).step_by(2) { - self.get_line(&mut sprite, data_address + j, line_number); - line_number += 1; - } - - return sprite; - } - - - fn draw_window_frame_buffer(&mut self, line:&mut [Color;SCREEN_WIDTH]) { - if !self.window_enable || !self.background_enabled || self.current_line_drawn < self.window_scroll.y{ - return; - } - - if self.current_line_drawn == self.window_scroll.y{ - self.window_active = true; - } - - if !self.window_active { - return; - } - - if self.window_scroll.x as usize > SCREEN_WIDTH { - return; - } - - let address = if self.window_tile_map_address { - 0x9C00 - } else { - 0x9800 - }; - let mut line_sprites:Vec = Vec::with_capacity(BG_SPRITES_PER_LINE as usize); - let index = ((self.window_line_counter) / 8) as u16; - if self.window_tile_background_map_data_address { - for i in 0..BG_SPRITES_PER_LINE { - let chr: u8 = self.read_vram(address + (index*BG_SPRITES_PER_LINE) + i); - let sprite = self.get_normal_sprite(chr, 0x8000); - line_sprites.push(sprite); - } - } - else { - for i in 0..BG_SPRITES_PER_LINE { - let mut chr: u8 = self.read_vram(address + (index*BG_SPRITES_PER_LINE) + i); - chr = chr.wrapping_add(0x80); - let sprite = self.get_normal_sprite(chr, 0x8800); - line_sprites.push(sprite); - } - } - - let mut drawn_line:[Color; 256] = [Color::default();256]; - - let sprite_line = ( self.window_line_counter) % NORMAL_SPRITE_HIEGHT; - for i in 0..line_sprites.len(){ - for j in 0..SPRITE_WIDTH{ - let pixel = line_sprites[i].pixels[((sprite_line * SPRITE_WIDTH) + j) as usize]; - drawn_line[(i * SPRITE_WIDTH as usize) + j as usize] = self.get_bg_color(pixel); - } - } - - for i in self.window_scroll.x as usize..SCREEN_WIDTH{ - line[(i as usize)] = drawn_line[i - self.window_scroll.x as usize]; - } - - self.window_line_counter += 1; - } - - fn draw_objects_frame_buffer(&self, line:&mut [Color;SCREEN_WIDTH]){ - if !self.sprite_enable{ - return; - } - - let currrent_line = self.current_line_drawn; - - let mut obj_attributes = Vec::with_capacity(OBJ_PER_LINE as usize); - - for i in (0..OAM_SIZE).step_by(4){ - if obj_attributes.len() >= OBJ_PER_LINE{ - break; - } - - let end_y = self.sprite_attribute_table[i as usize]; - let end_x = self.sprite_attribute_table[(i + 1) as usize]; - let start_y = cmp::max(0, (end_y as i16) - SPRITE_MAX_HEIGHT as i16) as u8; - - //cheks if this sprite apears in this line - if currrent_line >= end_y || currrent_line < start_y || end_x == 0 || end_x >=168{ - continue; - } - - //end_y is is the upper y value of the sprite + 16 lines and normal sprite is 8 lines. - //so checking if this sprite shouldnt be drawn - //on extended sprite end_y should be within all the values of current line - if !self.sprite_extended && end_y - currrent_line <= 8{ - continue; } - let tile_number = self.sprite_attribute_table[(i + 2) as usize]; - let attributes = self.sprite_attribute_table[(i + 3) as usize]; - - obj_attributes.push(SpriteAttribute::new(end_y, end_x, tile_number, attributes)); } + } - //sprites that occurs first in the oam memory draws first so im reversing it so the first ones will be last and will - //draw onto the last ones. - obj_attributes.reverse(); - //ordering this from the less priority to the higher where the smaller x the priority higher. - obj_attributes.sort_by(|a, b| b.x.cmp(&a.x)); - - for obj_attribute in &obj_attributes{ - let mut sprite = self.get_sprite(obj_attribute.tile_number, 0x8000, self.sprite_extended); - - if obj_attribute.flip_y { - sprite.flip_y(); + fn try_push_to_lcd(&mut self){ + if !(self.bg_fetcher.fifo.len() == 0){ + if !self.scanline_started{ + // discard the next pixel in the bg fifo + // the bg fifo should start with 8 pixels and not push more untill its empty again + if FIFO_SIZE as usize - self.bg_fetcher.fifo.len() >= self.bg_pos.x as usize % FIFO_SIZE as usize{ + self.scanline_started = true; + } + else{ + self.bg_fetcher.fifo.remove(); + return; + } } - if obj_attribute.flip_x{ - sprite.flip_x(); - } - - let end_x = cmp::min(obj_attribute.x, SCREEN_WIDTH as u8); - let start_x = cmp::max(0, (end_x as i16) - SPRITE_WIDTH as i16) as u8; - let start_y = cmp::max(0, (obj_attribute.y as i16) - SPRITE_MAX_HEIGHT as i16) as u8; - let sprite_line = currrent_line - start_y; + let bg_pixel_color_num = self.bg_fetcher.fifo.remove(); + let bg_pixel = self.bg_color_mapping[bg_pixel_color_num as usize]; + let pixel = if !(self.sprite_fetcher.fifo.len() == 0){ + let sprite_color_num = self.sprite_fetcher.fifo.remove(); + let pixel_oam_attribute = &self.sprite_fetcher.oam_entries[sprite_color_num.1 as usize]; - for x in start_x..end_x{ - let pixel = sprite.get_pixel(sprite_line * SPRITE_WIDTH + (x - start_x)); - let color = self.get_obj_color(pixel, obj_attribute.palette_number); - - if let Some(c) = color{ - if !(obj_attribute.is_bg_priority && self.get_bg_color(0) != line[x as usize]){ - line[x as usize] = c + if sprite_color_num.0 == 0 || (pixel_oam_attribute.is_bg_priority && bg_pixel_color_num != 0){ + bg_pixel + } + else{ + let sprite_pixel = if pixel_oam_attribute.palette_number{ + self.obj_color_mapping1[sprite_color_num.0 as usize] } + else{ + self.obj_color_mapping0[sprite_color_num.0 as usize] + }; + + sprite_pixel.expect("Corruption in the object color pallete") } } - } - } - - fn get_bg_color(&self, color: u8) -> Color { - return self.bg_color_mapping[color as usize].clone(); - } + else{ + bg_pixel + }; - fn get_obj_color(&self, color:u8, pallet_bit_set:bool)->Option{ - return if pallet_bit_set{ - self.obj_color_mapping1[color as usize].clone() + self.push_lcd_buffer.push(pixel); + self.pixel_x_pos += 1; } - else{ - self.obj_color_mapping0[color as usize].clone() - }; } - - fn get_sprite(&self, mut index:u8, data_address:u16, extended:bool)->Box{ - let mut sprite:Box; - if extended{ - //ignore bit 0 - index = (index >> 1) << 1; - sprite = Box::new(ExtendedSprite::new()); - } - else{ - sprite = Box::new(NormalSprite::new()); - } - - let mut line_number = 0; - let start:u16 = index as u16 * SPRITE_SIZE_IN_MEMORY; - let end:u16 = start + ((sprite.size() as u16) *2); - let raw = Box::into_raw(sprite); - for j in (start .. end).step_by(2) { - self.get_line( raw, data_address + j, line_number); - line_number += 1; - } - unsafe{sprite = Box::from_raw(raw);} - - return sprite; - } - - fn get_line(&self, sprite:*mut dyn Sprite, address:u16, line_number:u8){ - let byte = self.read_vram(address); - let next = self.read_vram(address + 1); - for k in (0..SPRITE_WIDTH).rev() { - let mask = 1 << k; - let mut value = (byte & mask) >> k; - value |= ((next & mask) >> k) << 1; - let swaped = SPRITE_WIDTH - 1 - k; - unsafe{(*sprite).set_pixel(line_number * SPRITE_WIDTH + swaped, value);} - } - } -} +} \ No newline at end of file diff --git a/lib_gb/src/ppu/gfx_device.rs b/lib_gb/src/ppu/gfx_device.rs new file mode 100644 index 00000000..400f51a2 --- /dev/null +++ b/lib_gb/src/ppu/gfx_device.rs @@ -0,0 +1,3 @@ +pub trait GfxDevice{ + fn swap_buffer(&self, buffer:&[u32]); +} \ No newline at end of file diff --git a/lib_gb/src/ppu/mod.rs b/lib_gb/src/ppu/mod.rs index e732249f..ca293728 100644 --- a/lib_gb/src/ppu/mod.rs +++ b/lib_gb/src/ppu/mod.rs @@ -3,7 +3,6 @@ pub mod ppu_state; pub mod color; pub mod colors; pub mod ppu_register_updater; -mod normal_sprite; -mod sprite_attribute; -mod extended_sprite; -mod sprite; \ No newline at end of file +pub mod fifo; +pub mod gfx_device; +mod sprite_attribute; \ No newline at end of file diff --git a/lib_gb/src/ppu/normal_sprite.rs b/lib_gb/src/ppu/normal_sprite.rs deleted file mode 100644 index 5da6832b..00000000 --- a/lib_gb/src/ppu/normal_sprite.rs +++ /dev/null @@ -1,67 +0,0 @@ -use super::sprite::Sprite; - -pub struct NormalSprite { - pub pixels: [u8; 64] -} - -impl NormalSprite { - const SIZE:u8 = 8; - - pub fn new() -> NormalSprite { - NormalSprite { pixels: [0; 64] } - } - - fn copy_pixels(sprite:&mut NormalSprite, index:usize, pixels:&[u8]){ - for i in 0..pixels.len(){ - sprite.pixels[index * 8 + i] = pixels[i]; - } - } -} - -impl Clone for NormalSprite{ - fn clone(&self)->Self{ - NormalSprite{ - pixels:self.pixels - } - } -} - -impl Sprite for NormalSprite{ - fn size(&self)->u8 {Self::SIZE} - - fn get_pixel(&self, pos: u8)->u8{ - self.pixels[pos as usize] - } - - fn set_pixel(&mut self, pos: u8, pixel: u8){ - self.pixels[pos as usize] = pixel; - } - - fn flip_x(&mut self){ - let mut fliiped = NormalSprite::new(); - - for y in 0..8{ - let line = &self.pixels[y*8 .. (y+1)*8]; - for x in 0..4{ - fliiped.pixels[y*8 + x] = line[7-x]; - fliiped.pixels[y*8 + (7-x)] = line[x]; - } - } - - *self = fliiped; - } - - fn flip_y(&mut self){ - let mut flipped = NormalSprite::new(); - for y in 0..4{ - let upper_line = &self.pixels[y*8..(y+1)*8]; - let opposite_index = 7-y; - let lower_line = &self.pixels[opposite_index*8..(opposite_index+1)*8]; - - Self::copy_pixels(&mut flipped,y, lower_line); - Self::copy_pixels(&mut flipped,opposite_index, upper_line); - } - - *self = flipped; - } -} \ No newline at end of file diff --git a/lib_gb/src/ppu/ppu_register_updater.rs b/lib_gb/src/ppu/ppu_register_updater.rs index 44d28a72..2157ac41 100644 --- a/lib_gb/src/ppu/ppu_register_updater.rs +++ b/lib_gb/src/ppu/ppu_register_updater.rs @@ -1,20 +1,20 @@ use crate::utils::bit_masks::*; -use super::{ gb_ppu::GbPpu, color::*, colors::*}; +use super::{color::*, colors::*, gb_ppu::GbPpu, gfx_device::GfxDevice}; const WX_OFFSET:u8 = 7; -pub fn handle_lcdcontrol_register( register:u8, ppu:&mut GbPpu){ - ppu.screen_enable = (register & BIT_7_MASK) != 0; - ppu.window_tile_map_address = (register & BIT_6_MASK) != 0; - ppu.window_enable = (register & BIT_5_MASK) != 0; - ppu.window_tile_background_map_data_address = (register & BIT_4_MASK) != 0; - ppu.background_tile_map_address = (register & BIT_3_MASK) != 0; - ppu.sprite_extended = (register & BIT_2_MASK) != 0; - ppu.sprite_enable = (register & BIT_1_MASK) != 0; - ppu.background_enabled = (register & BIT_0_MASK) != 0; +pub fn handle_lcdcontrol_register( register:u8, ppu:&mut GbPpu){ + if ppu.lcd_control & BIT_7_MASK != 0 && register & BIT_7_MASK == 0{ + ppu.turn_off(); + } + else if ppu.lcd_control & BIT_7_MASK == 0 && register & BIT_7_MASK != 0{ + ppu.turn_on(); + } + + ppu.lcd_control = register; } -pub fn update_stat_register(register:u8, ppu: &mut GbPpu){ +pub fn update_stat_register(register:u8, ppu: &mut GbPpu){ ppu.h_blank_interrupt_request = register & BIT_3_MASK != 0; ppu.v_blank_interrupt_request = register & BIT_4_MASK != 0; ppu.oam_search_interrupt_request = register & BIT_5_MASK != 0; @@ -23,17 +23,12 @@ pub fn update_stat_register(register:u8, ppu: &mut GbPpu){ ppu.stat_register = register & 0b111_1000; } -pub fn handle_scroll_registers(scroll_x:u8, scroll_y:u8, ppu: &mut GbPpu){ - ppu.background_scroll.x = scroll_x; - ppu.background_scroll.y = scroll_y; +pub fn set_scx(ppu: &mut GbPpu, value:u8){ + ppu.bg_pos.x = value; } -pub fn set_scx(ppu: &mut GbPpu, value:u8){ - ppu.background_scroll.x = value; -} - -pub fn set_scy(ppu:&mut GbPpu, value:u8){ - ppu.background_scroll.y = value; +pub fn set_scy(ppu:&mut GbPpu, value:u8){ + ppu.bg_pos.y = value; } pub fn handle_bg_pallet_register(register:u8, pallet:&mut [Color;4] ){ @@ -60,27 +55,27 @@ fn get_matching_color(number:u8)->Color{ }; } -pub fn handle_wy_register(register:u8, ppu:&mut GbPpu){ - ppu.window_scroll.y = register; +pub fn handle_wy_register(register:u8, ppu:&mut GbPpu){ + ppu.window_pos.y = register; } -pub fn handle_wx_register(register:u8, ppu:&mut GbPpu){ +pub fn handle_wx_register(register:u8, ppu:&mut GbPpu){ if register < WX_OFFSET{ - ppu.window_scroll.x = 0; + ppu.window_pos.x = 0; } else{ - ppu.window_scroll.x = register - WX_OFFSET; + ppu.window_pos.x = register - WX_OFFSET; } } -pub fn get_ly(ppu:&GbPpu)->u8{ - ppu.current_line_drawn +pub fn get_ly(ppu:&GbPpu)->u8{ + ppu.ly_register } -pub fn get_stat(ppu:&GbPpu)->u8{ +pub fn get_stat(ppu:&GbPpu)->u8{ ppu.stat_register } -pub fn set_lyc(ppu:&mut GbPpu, value:u8){ +pub fn set_lyc(ppu:&mut GbPpu, value:u8){ ppu.lyc_register = value; -} +} \ No newline at end of file diff --git a/lib_gb/src/ppu/sprite.rs b/lib_gb/src/ppu/sprite.rs deleted file mode 100644 index 2eeeab2e..00000000 --- a/lib_gb/src/ppu/sprite.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub trait Sprite{ - fn size(&self)->u8; - fn flip_x(&mut self); - fn flip_y(&mut self); - fn get_pixel(&self, pos:u8)->u8; - fn set_pixel(&mut self, pos:u8, pixel:u8); -} - -pub fn copy_pixels(sprite:&mut dyn Sprite, index:u8, pixels:&[u8]){ - for i in 0..pixels.len(){ - sprite.set_pixel(index * 8 + i as u8, pixels[i]) ; - } -} \ No newline at end of file diff --git a/lib_gb/src/utils/fixed_size_queue.rs b/lib_gb/src/utils/fixed_size_queue.rs new file mode 100644 index 00000000..ef8a0760 --- /dev/null +++ b/lib_gb/src/utils/fixed_size_queue.rs @@ -0,0 +1,68 @@ +use super::create_default_array; + +pub struct FixedSizeQueue{ + data: [T;SIZE], + length: usize, +} + +impl FixedSizeQueue{ + pub fn new()->Self{ + Self{ + data:create_default_array(), + length:0, + } + } + + pub fn push(&mut self, t:T){ + if self.length < SIZE{ + self.data[self.length] = t; + self.length += 1; + } + else{ + std::panic!("queue is already full, size: {}", SIZE); + } + } + + pub fn remove(&mut self)->T{ + if self.length > 0{ + let t = self.data[0]; + for i in 1..self.length{ + self.data[i - 1] = self.data[i]; + } + self.length -= 1; + return t; + } + + std::panic!("The fifo is empty"); + } + + pub fn clear(&mut self){ + self.length = 0; + } + + pub fn len(&self)->usize{ + self.length + } +} + +impl std::ops::Index for FixedSizeQueue{ + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + if index < self.length{ + return &self.data[index]; + } + + std::panic!("Index is out of range"); + } +} + +impl std::ops::IndexMut for FixedSizeQueue{ + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + if index < self.length{ + return &mut self.data[index]; + } + + std::panic!("Index is out of range"); + } +} \ No newline at end of file diff --git a/lib_gb/src/utils/mod.rs b/lib_gb/src/utils/mod.rs index ef58f315..0dc5325e 100644 --- a/lib_gb/src/utils/mod.rs +++ b/lib_gb/src/utils/mod.rs @@ -1,5 +1,25 @@ +use std::mem::MaybeUninit; + pub mod vec2; pub mod memory_registers; pub mod bit_masks; +pub mod fixed_size_queue; + +pub const GB_FREQUENCY:u32 = 4_194_304; + +pub fn create_default_array()->[T;SIZE]{ + create_array(||T::default()) +} + +pub fn create_arrayT,const SIZE:usize>(func:F)->[T;SIZE]{ + let mut data: [MaybeUninit; SIZE] = unsafe{MaybeUninit::uninit().assume_init()}; -pub const GB_FREQUENCY:u32 = 4_194_304; \ No newline at end of file + for elem in &mut data[..]{ + *elem = MaybeUninit::new(func()); + } + unsafe{ + let casted_data = std::ptr::read(&data as *const [MaybeUninit;SIZE] as *const [T;SIZE]); + std::mem::forget(data); + return casted_data; + } +} \ No newline at end of file diff --git a/lib_gb/tests/fixed_size_queue_tests.rs b/lib_gb/tests/fixed_size_queue_tests.rs new file mode 100644 index 00000000..3e2e7391 --- /dev/null +++ b/lib_gb/tests/fixed_size_queue_tests.rs @@ -0,0 +1,53 @@ +use lib_gb::utils::fixed_size_queue::FixedSizeQueue; + +#[test] +fn test_fifo(){ + let mut fifo = FixedSizeQueue::::new(); + fifo.push(10); + fifo.push(22); + + assert_eq!(fifo.len(), 2); + assert_eq!(fifo[0], 10); + assert_eq!(fifo[1], 22); + + fifo.remove(); + assert_eq!(fifo.len(), 1); + assert_eq!(fifo[0], 22); + + fifo[0] = 21; + assert_eq!(fifo[0], 21); +} + +#[test] +#[should_panic] +fn panic_on_fifo_full(){ + let mut fifo = FixedSizeQueue::::new(); + fifo.push(1); + fifo.push(2); + fifo.push(3); + + //should panic + fifo.push(1); +} + +#[test] +#[should_panic] +fn panic_on_get_fifo_index_out_of_range(){ + let mut fifo = FixedSizeQueue::::new(); + fifo.push(1); + fifo.push(2); + + //should panic + let _ = fifo[2]; +} + +#[test] +#[should_panic] +fn panic_on_fifo_set_index_out_of_range(){ + let mut fifo = FixedSizeQueue::::new(); + fifo.push(1); + fifo.push(2); + + //should panic + fifo[2] = 4; +} \ No newline at end of file