Compare commits

...

4 Commits

Author SHA1 Message Date
10848d28eb Refactor interrupts 2021-11-25 21:13:55 -05:00
aa88a6498e more WIP for sound 2021-11-25 20:09:17 -05:00
7fe522ea3b Timer tests 2021-11-23 09:21:25 -05:00
23e7607319 Per pixel rendering PPU 2021-11-22 20:55:51 -05:00
11 changed files with 555 additions and 230 deletions

325
Cargo.lock generated
View File

@ -23,6 +23,28 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "alsa"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75c4da790adcb2ce5e758c064b4f3ec17a30349f9961d3e5e6c9688b052a9e18"
dependencies = [
"alsa-sys",
"bitflags",
"libc",
"nix 0.20.0",
]
[[package]]
name = "alsa-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
dependencies = [
"libc",
"pkg-config",
]
[[package]] [[package]]
name = "andrew" name = "andrew"
version = "0.3.1" version = "0.3.1"
@ -68,6 +90,25 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bindgen"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
]
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.5.2" version = "0.5.2"
@ -113,6 +154,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]] [[package]]
name = "calloop" name = "calloop"
version = "0.6.5" version = "0.6.5"
@ -128,6 +175,24 @@ name = "cc"
version = "1.0.71" version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
dependencies = [
"jobserver",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom 5.1.2",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -147,6 +212,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "clang-sys"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90"
dependencies = [
"glob",
"libc",
"libloading 0.7.1",
]
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.24.0" version = "0.24.0"
@ -188,6 +264,16 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "combine"
version = "4.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b2f5d0ee456f3928812dfc8c6d9a1d592b98678f6d56db9b0cd2b7bc6c8db5"
dependencies = [
"bytes",
"memchr",
]
[[package]] [[package]]
name = "copyless" name = "copyless"
version = "0.1.5" version = "0.1.5"
@ -276,6 +362,50 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "coreaudio-rs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88"
dependencies = [
"bitflags",
"coreaudio-sys",
]
[[package]]
name = "coreaudio-sys"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b7e3347be6a09b46aba228d6608386739fb70beff4f61e07422da87b0bb31fa"
dependencies = [
"bindgen",
]
[[package]]
name = "cpal"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98f45f0a21f617cd2c788889ef710b63f075c949259593ea09c826f1e47a2418"
dependencies = [
"alsa",
"core-foundation-sys 0.8.3",
"coreaudio-rs",
"jni",
"js-sys",
"lazy_static",
"libc",
"mach",
"ndk 0.3.0",
"ndk-glue 0.3.0",
"nix 0.20.0",
"oboe",
"parking_lot",
"stdweb",
"thiserror",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "crossbeam" name = "crossbeam"
version = "0.8.1" version = "0.8.1"
@ -505,6 +635,12 @@ dependencies = [
"wasi", "wasi",
] ]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.11.0" version = "0.11.0"
@ -626,12 +762,35 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "jni"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
dependencies = [
"cesu8",
"combine",
"jni-sys",
"log",
"thiserror",
"walkdir",
]
[[package]] [[package]]
name = "jni-sys" name = "jni-sys"
version = "0.3.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.55" version = "0.3.55"
@ -657,6 +816,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.105" version = "0.2.105"
@ -701,6 +866,15 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "mach"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@ -818,6 +992,19 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "ndk"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d64d6af06fde0e527b1ba5c7b79a6cc89cfc46325b0b2887dffe8f70197e0c3c"
dependencies = [
"bitflags",
"jni-sys",
"ndk-sys",
"num_enum",
"thiserror",
]
[[package]] [[package]]
name = "ndk-glue" name = "ndk-glue"
version = "0.3.0" version = "0.3.0"
@ -827,7 +1014,21 @@ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
"log", "log",
"ndk", "ndk 0.3.0",
"ndk-macro",
"ndk-sys",
]
[[package]]
name = "ndk-glue"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e9e94628f24e7a3cb5b96a2dc5683acd9230bf11991c2a1677b87695138420"
dependencies = [
"lazy_static",
"libc",
"log",
"ndk 0.4.0",
"ndk-macro", "ndk-macro",
"ndk-sys", "ndk-sys",
] ]
@ -875,6 +1076,16 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.0.0" version = "7.0.0"
@ -895,6 +1106,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"
@ -945,6 +1167,29 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "oboe"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e15e22bc67e047fe342a32ecba55f555e3be6166b04dd157cd0f803dfa9f48e1"
dependencies = [
"jni",
"ndk 0.4.0",
"ndk-glue 0.4.0",
"num-derive",
"num-traits",
"oboe-sys",
]
[[package]]
name = "oboe-sys"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338142ae5ab0aaedc8275aa8f67f460e43ae0fca76a695a742d56da0a269eadc"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.8.0" version = "1.8.0"
@ -985,6 +1230,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -1017,12 +1268,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb20dcc30536a1508e75d47dd0e399bb2fe7354dcf35cda9127f2bf1ed92e30e" checksum = "bb20dcc30536a1508e75d47dd0e399bb2fe7354dcf35cda9127f2bf1ed92e30e"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "0.1.5" version = "0.1.5"
@ -1066,46 +1311,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]] [[package]]
name = "range-alloc" name = "range-alloc"
version = "0.1.2" version = "0.1.2"
@ -1167,14 +1372,20 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
name = "rmg-001" name = "rmg-001"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cpal",
"env_logger", "env_logger",
"log", "log",
"pixels", "pixels",
"rand",
"winit", "winit",
"winit_input_helper", "winit_input_helper",
] ]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rusttype" name = "rusttype"
version = "0.9.2" version = "0.9.2"
@ -1221,6 +1432,12 @@ version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]] [[package]]
name = "slotmap" name = "slotmap"
version = "1.0.6" version = "1.0.6"
@ -1265,6 +1482,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "stdweb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.9.3" version = "0.9.3"
@ -1667,8 +1890,8 @@ dependencies = [
"log", "log",
"mio", "mio",
"mio-misc", "mio-misc",
"ndk", "ndk 0.3.0",
"ndk-glue", "ndk-glue 0.3.0",
"ndk-sys", "ndk-sys",
"objc", "objc",
"parking_lot", "parking_lot",
@ -1707,7 +1930,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7"
dependencies = [ dependencies = [
"nom", "nom 7.0.0",
] ]
[[package]] [[package]]

View File

@ -10,7 +10,7 @@ optimize = ["log/release_max_level_warn"]
default = ["optimize"] default = ["optimize"]
[dependencies] [dependencies]
rand = "0.8" cpal = "0.13"
env_logger = "0.9" env_logger = "0.9"
log = "0.4" log = "0.4"
pixels = "0.7" pixels = "0.7"

View File

@ -44,4 +44,5 @@ This project would have been completely impossible without all the documentation
- Opcodes behaviour: https://rgbds.gbdev.io/docs/v0.5.1/gbz80.7 - Opcodes behaviour: https://rgbds.gbdev.io/docs/v0.5.1/gbz80.7
- Blargg's test ROMs: https://github.com/retrio/gb-test-roms - Blargg's test ROMs: https://github.com/retrio/gb-test-roms
- Mooneye test ROMs: https://github.com/Gekkio/mooneye-test-suite - Mooneye test ROMs: https://github.com/Gekkio/mooneye-test-suite
- Sound emulation guide: https://nightshade256.github.io/2021/03/27/gb-sound-emulation.html
- The Ultimate Gameboy talk: https://youtu.be/HyzD8pNlpwI - The Ultimate Gameboy talk: https://youtu.be/HyzD8pNlpwI

View File

@ -7,9 +7,14 @@ use crate::ppu::{
PPU, PPU,
DMA_ADDRESS, DMA_ADDRESS,
}; };
use crate::cpu::{Interrupt};
use crate::timer::{Timer}; use crate::timer::{Timer};
use crate::joypad::{Joypad, JOYPAD_ADDRESS}; use crate::joypad::{Joypad, JOYPAD_ADDRESS};
use crate::sound::{Sound};
use crate::interrupts::{
Interrupts,
INTERRUPT_ENABLE_ADDRESS,
INTERRUPT_FLAG_ADDRESS,
};
pub const BANK_ZERO: RangeInclusive<u16> = 0x0000..=0x3FFF; pub const BANK_ZERO: RangeInclusive<u16> = 0x0000..=0x3FFF;
pub const BANK_SWITCHABLE: RangeInclusive<u16> = 0x4000..=0x7FFF; pub const BANK_SWITCHABLE: RangeInclusive<u16> = 0x4000..=0x7FFF;
@ -22,9 +27,6 @@ pub const SPRITE_ATTRIBUTE_TABLE: RangeInclusive<u16> = 0xFE00..=0xFE9F;
pub const NOT_USABLE: RangeInclusive<u16> = 0xFEA0..=0xFEFF; pub const NOT_USABLE: RangeInclusive<u16> = 0xFEA0..=0xFEFF;
pub const IO_REGISTERS: RangeInclusive<u16> = 0xFF00..=0xFF7F; pub const IO_REGISTERS: RangeInclusive<u16> = 0xFF00..=0xFF7F;
pub const HIGH_RAM: RangeInclusive<u16> = 0xFF80..=0xFFFE; pub const HIGH_RAM: RangeInclusive<u16> = 0xFF80..=0xFFFE;
pub const INTERRUPT_ENABLE_REGISTER: RangeInclusive<u16> = 0xFFFF..=0xFFFF;
pub const INTERRUPT_ENABLE_ADDRESS: u16 = 0xFFFF;
pub const INTERRUPT_FLAG_ADDRESS: u16 = 0xFF0F;
pub struct Bus { pub struct Bus {
data: [u8; 0x10000], data: [u8; 0x10000],
@ -32,6 +34,8 @@ pub struct Bus {
pub ppu: PPU, pub ppu: PPU,
pub joypad: Joypad, pub joypad: Joypad,
pub timer: Timer, pub timer: Timer,
pub sound: Sound,
pub interrupts: Interrupts,
} }
impl Bus { impl Bus {
@ -55,6 +59,8 @@ impl Bus {
ppu: PPU::new(), ppu: PPU::new(),
joypad: Joypad::new(), joypad: Joypad::new(),
timer: Timer::new(), timer: Timer::new(),
sound: Sound::new(),
interrupts: Interrupts::new(),
}; };
// Hardware registers after the bootrom // Hardware registers after the bootrom
@ -87,13 +93,15 @@ impl Bus {
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) { if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
return self.rom.read(address); return self.rom.read(address);
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { } else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
return 0b11100000 | self.data[address as usize]; return self.interrupts.read(address);
} else if VIDEO_RAM.contains(&address) { } else if VIDEO_RAM.contains(&address) {
return self.ppu.read_vram(address); return self.ppu.read_vram(address);
} else if SPRITE_ATTRIBUTE_TABLE.contains(&address) { } else if SPRITE_ATTRIBUTE_TABLE.contains(&address) {
return self.ppu.read_oam(address); return self.ppu.read_oam(address);
} else if PPU::is_io_register(address) { } else if PPU::is_io_register(address) {
return self.ppu.get_register(address); return self.ppu.get_register(address);
} else if Sound::is_io_register(address) {
return self.sound.get_register(address);
} else if address == JOYPAD_ADDRESS { } else if address == JOYPAD_ADDRESS {
return self.joypad.read(self.data[address as usize]); return self.joypad.read(self.data[address as usize]);
} else if Timer::is_io_register(address) { } else if Timer::is_io_register(address) {
@ -113,6 +121,8 @@ impl Bus {
if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) { if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) {
self.rom.write(address, data); self.rom.write(address, data);
} else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS {
self.interrupts.write(address, data);
} else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) { } else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) {
self.data[address as usize] = data; self.data[address as usize] = data;
// Copy to the ECHO RAM // Copy to the ECHO RAM
@ -126,6 +136,8 @@ impl Bus {
self.data[(WORK_RAM_1.min().unwrap() + (address - ECHO_RAM.min().unwrap())) as usize] = data; // Copy to the working RAM self.data[(WORK_RAM_1.min().unwrap() + (address - ECHO_RAM.min().unwrap())) as usize] = data; // Copy to the working RAM
} else if Timer::is_io_register(address) { } else if Timer::is_io_register(address) {
self.timer.set_register(address, data); self.timer.set_register(address, data);
} else if Sound::is_io_register(address) {
self.sound.set_register(address, data);
} else if address == JOYPAD_ADDRESS { } else if address == JOYPAD_ADDRESS {
let byte = self.data[address as usize]; let byte = self.data[address as usize];
self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111); self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111);
@ -149,18 +161,9 @@ impl Bus {
} }
} }
pub fn force_write(&mut self, address: u16, data: u8) {
self.data[address as usize] = data;
}
pub fn write_16bit(&mut self, address: u16, data: u16) { pub fn write_16bit(&mut self, address: u16, data: u16) {
let bytes = data.to_le_bytes(); let bytes = data.to_le_bytes();
self.write(address, bytes[0]); self.write(address, bytes[0]);
self.write(address.wrapping_add(1), bytes[1]); self.write(address.wrapping_add(1), bytes[1]);
} }
pub fn set_interrupt_flag(&mut self, interrupt: Interrupt, val: bool) {
let byte = self.read(INTERRUPT_FLAG_ADDRESS);
self.write(INTERRUPT_FLAG_ADDRESS, interrupt.set(byte, val));
}
} }

View File

@ -8,7 +8,12 @@ use crate::utils::{
sub_half_carry, sub_half_carry,
add_half_carry_16bit, add_half_carry_16bit,
}; };
use crate::bus::{Bus, INTERRUPT_ENABLE_ADDRESS, INTERRUPT_FLAG_ADDRESS}; use crate::bus::{Bus};
use crate::interrupts::{
Interrupt,
INTERRUPT_ENABLE_ADDRESS,
INTERRUPT_FLAG_ADDRESS,
};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum Register { pub enum Register {
@ -53,45 +58,6 @@ pub enum FlagRegister {
Carry, // Set if a carry was ocurrend from the last math operation or if register A is the smaller value when executing the CP instruction Carry, // Set if a carry was ocurrend from the last math operation or if register A is the smaller value when executing the CP instruction
} }
#[derive(Debug, Copy, Clone)]
pub enum Interrupt {
VBlank,
LCDSTAT,
Timer,
Serial,
Joypad,
}
impl Interrupt {
fn get_bit_index(&self) -> BitIndex {
match self {
Interrupt::VBlank => BitIndex::I0,
Interrupt::LCDSTAT => BitIndex::I1,
Interrupt::Timer => BitIndex::I2,
Interrupt::Serial => BitIndex::I3,
Interrupt::Joypad => BitIndex::I4,
}
}
pub fn get(&self, byte: u8) -> bool {
get_bit(byte, self.get_bit_index())
}
pub fn set(&self, byte: u8, val: bool) -> u8 {
set_bit(byte, val, self.get_bit_index())
}
pub fn get_vector(&self) -> u16 {
match self {
Interrupt::VBlank => 0x40,
Interrupt::LCDSTAT => 0x48,
Interrupt::Timer => 0x50,
Interrupt::Serial => 0x58,
Interrupt::Joypad => 0x60,
}
}
}
pub struct Registers { pub struct Registers {
a: u8, a: u8,
f: u8, f: u8,
@ -914,7 +880,7 @@ impl CPU {
pub fn handle_interrupt(&mut self, bus: &mut Bus, interrupt: Interrupt) { pub fn handle_interrupt(&mut self, bus: &mut Bus, interrupt: Interrupt) {
// println!("Interrupt: {:?}", interrupt); // println!("Interrupt: {:?}", interrupt);
bus.set_interrupt_flag(interrupt, false); bus.interrupts.set(interrupt, false);
self.ime = false; self.ime = false;
self.registers.decrement(Register::PC, 3); self.registers.decrement(Register::PC, 3);
self.exec(Opcode::CALL(OpcodeParameter::U16(interrupt.get_vector())), bus); self.exec(Opcode::CALL(OpcodeParameter::U16(interrupt.get_vector())), bus);

View File

@ -2,7 +2,8 @@
use winit_input_helper::WinitInputHelper; use winit_input_helper::WinitInputHelper;
use winit::event::{VirtualKeyCode}; use winit::event::{VirtualKeyCode};
use crate::cpu::{CPU, Cycles, Interrupt}; use crate::cpu::{CPU, Cycles};
use crate::interrupts::Interrupt;
use crate::bus::Bus; use crate::bus::Bus;
use crate::joypad::{Button}; use crate::joypad::{Button};
#[cfg(not(test))] #[cfg(not(test))]
@ -99,7 +100,7 @@ impl Emulator {
self.bus.joypad.release(Button::Select); self.bus.joypad.release(Button::Select);
} }
if change { if change {
self.bus.set_interrupt_flag(Interrupt::Joypad, true); self.bus.interrupts.request(Interrupt::Joypad);
} }
} }
@ -108,20 +109,8 @@ impl Emulator {
while self.cpu.get_cycles().to_t().0 <= cpu_cycles.0 { while self.cpu.get_cycles().to_t().0 <= cpu_cycles.0 {
self.cpu.run(&mut self.bus); self.cpu.run(&mut self.bus);
let cycles = self.cpu.get_last_op_cycles().to_t(); let cycles = self.cpu.get_last_op_cycles().to_t();
self.bus.ppu.do_cycles(cycles, frame_buffer); self.bus.ppu.do_cycles(&mut self.bus.interrupts, cycles, frame_buffer);
if self.bus.ppu.get_interrupt(Interrupt::VBlank) { self.bus.timer.do_cycles(&mut self.bus.interrupts, cycles);
self.bus.set_interrupt_flag(Interrupt::VBlank, true);
self.bus.ppu.set_interrupt(Interrupt::VBlank, false);
}
if self.bus.ppu.get_interrupt(Interrupt::LCDSTAT) {
self.bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
self.bus.ppu.set_interrupt(Interrupt::LCDSTAT, false);
}
self.bus.timer.do_cycles(cycles);
if self.bus.timer.get_interrupt() {
self.bus.set_interrupt_flag(Interrupt::Timer, true);
self.bus.timer.set_interrupt(false);
}
// 1 CPU cycle = 238.42ns // 1 CPU cycle = 238.42ns
// thread::sleep(time::Duration::from_nanos((self.cpu.get_last_op_cycles().0 * 238).try_into().unwrap())); // thread::sleep(time::Duration::from_nanos((self.cpu.get_last_op_cycles().0 * 238).try_into().unwrap()));
@ -135,20 +124,8 @@ impl Emulator {
while !exit { while !exit {
self.cpu.run(&mut self.bus); self.cpu.run(&mut self.bus);
let cycles = self.cpu.get_last_op_cycles().to_t(); let cycles = self.cpu.get_last_op_cycles().to_t();
self.bus.ppu.do_cycles(cycles, &mut frame); self.bus.ppu.do_cycles(&mut self.bus.interrupts, cycles, &mut frame);
if self.bus.ppu.get_interrupt(Interrupt::VBlank) { self.bus.timer.do_cycles(&mut self.bus.interrupts, cycles);
self.bus.set_interrupt_flag(Interrupt::VBlank, true);
self.bus.ppu.set_interrupt(Interrupt::VBlank, false);
}
if self.bus.ppu.get_interrupt(Interrupt::LCDSTAT) {
self.bus.set_interrupt_flag(Interrupt::LCDSTAT, true);
self.bus.ppu.set_interrupt(Interrupt::LCDSTAT, false);
}
self.bus.timer.do_cycles(cycles);
if self.bus.timer.get_interrupt() {
self.bus.set_interrupt_flag(Interrupt::Timer, true);
self.bus.timer.set_interrupt(false);
}
// exit = self.cpu.get_exec_calls_count() >= 1258895; // log 1 // exit = self.cpu.get_exec_calls_count() >= 1258895; // log 1
exit = self.cpu.get_exec_calls_count() >= 161502; // log 2 exit = self.cpu.get_exec_calls_count() >= 161502; // log 2

90
src/interrupts.rs Normal file
View File

@ -0,0 +1,90 @@
use crate::utils::{
BitIndex,
get_bit,
set_bit,
};
pub const INTERRUPT_ENABLE_ADDRESS: u16 = 0xFFFF;
pub const INTERRUPT_FLAG_ADDRESS: u16 = 0xFF0F;
#[derive(Debug, Copy, Clone)]
pub enum Interrupt {
VBlank,
LCDSTAT,
Timer,
Serial,
Joypad,
}
impl Interrupt {
fn get_bit_index(&self) -> BitIndex {
match self {
Interrupt::VBlank => BitIndex::I0,
Interrupt::LCDSTAT => BitIndex::I1,
Interrupt::Timer => BitIndex::I2,
Interrupt::Serial => BitIndex::I3,
Interrupt::Joypad => BitIndex::I4,
}
}
pub fn get(&self, byte: u8) -> bool {
get_bit(byte, self.get_bit_index())
}
pub fn set(&self, byte: u8, val: bool) -> u8 {
set_bit(byte, val, self.get_bit_index())
}
pub fn get_vector(&self) -> u16 {
match self {
Interrupt::VBlank => 0x40,
Interrupt::LCDSTAT => 0x48,
Interrupt::Timer => 0x50,
Interrupt::Serial => 0x58,
Interrupt::Joypad => 0x60,
}
}
}
pub struct Interrupts {
interrupt_enable: u8,
interrupt_flag: u8,
}
impl Interrupts {
pub fn new() -> Self {
Self {
interrupt_enable: 0,
interrupt_flag: 0,
}
}
pub fn read(&self, address: u16) -> u8 {
let byte = match address {
INTERRUPT_ENABLE_ADDRESS => self.interrupt_enable,
INTERRUPT_FLAG_ADDRESS => self.interrupt_flag,
_ => unreachable!(),
};
0b11100000 | byte
}
pub fn write(&mut self, address: u16, data: u8) {
match address {
INTERRUPT_ENABLE_ADDRESS => self.interrupt_enable = data,
INTERRUPT_FLAG_ADDRESS => self.interrupt_flag = data,
_ => unreachable!(),
};
}
pub fn set(&mut self, interrupt: Interrupt, val: bool) {
self.interrupt_flag = interrupt.set(self.interrupt_flag, val);
}
pub fn get(&self, interrupt: Interrupt) -> bool {
interrupt.get(self.interrupt_flag)
}
pub fn request(&mut self, interrupt: Interrupt) {
self.set(interrupt, true)
}
}

View File

@ -5,6 +5,7 @@ pub mod timer;
pub mod sound; pub mod sound;
pub mod rom; pub mod rom;
pub mod bus; pub mod bus;
pub mod interrupts;
pub mod joypad; pub mod joypad;
pub mod emulator; pub mod emulator;
pub mod render; pub mod render;

View File

@ -4,7 +4,8 @@ use crate::utils::{
set_bit, set_bit,
}; };
use crate::bus::{SPRITE_ATTRIBUTE_TABLE}; use crate::bus::{SPRITE_ATTRIBUTE_TABLE};
use crate::cpu::{Cycles, Interrupt}; use crate::cpu::{Cycles};
use crate::interrupts::{Interrupts, Interrupt};
pub const LCD_WIDTH: u32 = 160; pub const LCD_WIDTH: u32 = 160;
pub const LCD_HEIGHT: u32 = 144; pub const LCD_HEIGHT: u32 = 144;
@ -134,7 +135,6 @@ struct Sprite {
x_flip: bool, x_flip: bool,
y_flip: bool, y_flip: bool,
over_bg: bool, over_bg: bool,
is_long: bool,
bit_pixels: Option<[u8; 8]>, bit_pixels: Option<[u8; 8]>,
} }
@ -143,7 +143,11 @@ impl Sprite {
self.x self.x
} }
pub fn get_pixel(&mut self, lcd_x: u8, lcd_y: u8, vram: &[u8], last_bg_index: u8) -> Option<(Pixel, bool)> { pub fn get_pixel(&mut self, lcd_x: u8, lcd_y: u8, vram: &[u8], last_bg_index: u8, lcd_control: u8) -> Option<(Pixel, bool)> {
if !LCDControl::ObjectEnable.get(lcd_control) {
return None;
}
if lcd_x < self.x.saturating_sub(8) || lcd_x >= self.x { if lcd_x < self.x.saturating_sub(8) || lcd_x >= self.x {
return None; return None;
} }
@ -152,7 +156,9 @@ impl Sprite {
return None; return None;
} }
let height: u8 = match self.is_long { let is_long = LCDControl::ObjectSize.get(lcd_control);
let height: u8 = match is_long {
true => 16, true => 16,
false => 8, false => 8,
}; };
@ -176,9 +182,9 @@ impl Sprite {
None => { None => {
let mut tile_number = self.tile_number; let mut tile_number = self.tile_number;
if self.is_long && x <= 7 { if is_long && x <= 7 {
tile_number = tile_number & 0xFE; tile_number = tile_number & 0xFE;
} else if self.is_long && x > 7 { } else if is_long && x > 7 {
tile_number = tile_number | 0x01; tile_number = tile_number | 0x01;
} }
@ -205,11 +211,10 @@ impl Sprite {
pub struct PPU { pub struct PPU {
state: bool, state: bool,
vblank_request: bool,
lcdstat_request: bool,
background_priority: bool, background_priority: bool,
window_enable: bool, window_enable: bool,
lcd_enable: bool, lcd_enable: bool,
window_drawn: bool,
cycles: Cycles, cycles: Cycles,
sprite_buffer: Vec<Sprite>, sprite_buffer: Vec<Sprite>,
window_y_counter: u8, window_y_counter: u8,
@ -219,6 +224,7 @@ pub struct PPU {
current_background_pixels: Option<[u8; 8]>, current_background_pixels: Option<[u8; 8]>,
current_window_pixels: Option<[u8; 8]>, current_window_pixels: Option<[u8; 8]>,
lcd_y: u8, lcd_y: u8,
lcd_x: u8,
scroll_x: u8, scroll_x: u8,
scroll_y: u8, scroll_y: u8,
window_x: u8, window_x: u8,
@ -232,10 +238,9 @@ impl PPU {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
state: false, state: false,
vblank_request: false,
lcdstat_request: false,
background_priority: false, background_priority: false,
window_enable: false, window_enable: false,
window_drawn: false,
lcd_enable: false, lcd_enable: false,
cycles: Cycles(0), cycles: Cycles(0),
sprite_buffer: Vec::new(), sprite_buffer: Vec::new(),
@ -246,6 +251,7 @@ impl PPU {
current_background_pixels: None, current_background_pixels: None,
current_window_pixels: None, current_window_pixels: None,
lcd_y: 0, lcd_y: 0,
lcd_x: 0,
scroll_x: 0, scroll_x: 0,
scroll_y: 0, scroll_y: 0,
window_x: 0, window_x: 0,
@ -256,22 +262,6 @@ impl PPU {
} }
} }
pub fn set_interrupt(&mut self, interrupt: Interrupt, val: bool) {
match interrupt {
Interrupt::VBlank => self.vblank_request = val,
Interrupt::LCDSTAT => self.lcdstat_request = val,
_ => unreachable!(),
};
}
pub fn get_interrupt(&self, interrupt: Interrupt) -> bool {
match interrupt {
Interrupt::VBlank => self.vblank_request,
Interrupt::LCDSTAT => self.lcdstat_request,
_ => unreachable!(),
}
}
pub fn is_io_register(address: u16) -> bool { pub fn is_io_register(address: u16) -> bool {
address >= 0xFF40 && address <= 0xFF4B address >= 0xFF40 && address <= 0xFF4B
} }
@ -295,6 +285,8 @@ impl PPU {
pub fn get_register(&self, address: u16) -> u8 { pub fn get_register(&self, address: u16) -> u8 {
if address == LCD_CONTROL_ADDRESS { if address == LCD_CONTROL_ADDRESS {
return self.lcd_control; return self.lcd_control;
} else if address == LCD_Y_ADDRESS {
return self.lcd_y;
} }
self.io_registers[(address - 0xFF40) as usize] self.io_registers[(address - 0xFF40) as usize]
} }
@ -307,7 +299,7 @@ impl PPU {
// Check if LCD is being turned on or off // Check if LCD is being turned on or off
self.lcd_enable = get_bit(data, BitIndex::I7); self.lcd_enable = get_bit(data, BitIndex::I7);
if !get_bit(data, BitIndex::I7) || (get_bit(data, BitIndex::I7) && !get_bit(self.lcd_control, BitIndex::I7)) { if !get_bit(data, BitIndex::I7) || (get_bit(data, BitIndex::I7) && !get_bit(self.lcd_control, BitIndex::I7)) {
self.io_registers[LCD_Y_ADDRESS as usize - 0xFF40] = 0x00; self.lcd_y = 0x00;
// Set Hblank // Set Hblank
let byte = self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40]; let byte = self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40];
self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40] = byte & 0b11111100; self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40] = byte & 0b11111100;
@ -334,40 +326,35 @@ impl PPU {
self.cycles.0 += cycles.0; self.cycles.0 += cycles.0;
} }
pub fn do_cycles(&mut self, cycles: Cycles, frame_buffer: &mut [u8]) { pub fn do_cycles(&mut self, interrupts: &mut Interrupts, cycles: Cycles, frame_buffer: &mut [u8]) {
if !self.lcd_enable { if !self.lcd_enable {
self.increment_cycles(cycles); self.increment_cycles(cycles);
return; return;
} }
self.lcd_y = self.get_register(LCD_Y_ADDRESS);
if self.lcd_y < 144 { if self.lcd_y < 144 {
if self.cycles.0 <= 80 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM)) { if self.cycles.0 <= 80 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM)) {
// Mode 2 OAM scan // Mode 2 OAM scan
self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM), true); self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::SearchingOAM), true);
self.stat_interrupt(); self.stat_interrupt(interrupts);
self.oam_search(); self.oam_search();
} else if self.cycles.0 > 80 && self.cycles.0 <= 80 + 172 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { } else if self.cycles.0 > 80 && self.cycles.0 <= 80 + 172 {
// Mode 3 drawing pixel line. This could also last 289 cycles // Mode 3 drawing pixel line. This could also last 289 cycles
self.scroll_x = self.get_register(SCROLL_X_ADDRESS); if !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) {
self.scroll_y = self.get_register(SCROLL_Y_ADDRESS); self.window_drawn = false;
self.window_x = self.get_register(WINDOW_X_ADDRESS);
self.window_y = self.get_register(WINDOW_Y_ADDRESS);
self.window_enable = self.get_lcd_control(LCDControl::WindowEnable);
self.background_priority = self.get_lcd_control(LCDControl::BackgroundPriority);
self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD), true); self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD), true);
self.draw_line(frame_buffer); }
self.draw_line(cycles, frame_buffer);
} else if self.cycles.0 > 80 + 172 && self.cycles.0 <= 80 + 172 + 204 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank)) { } else if self.cycles.0 > 80 + 172 && self.cycles.0 <= 80 + 172 + 204 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank)) {
// Mode 0 Horizontal blank. This could last 87 or 204 cycles depending on the mode 3 // Mode 0 Horizontal blank. This could last 87 or 204 cycles depending on the mode 3
self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank), true); self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::HBlank), true);
self.stat_interrupt(); self.stat_interrupt(interrupts);
} }
} else if self.lcd_y >= 144 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank)) { } else if self.lcd_y >= 144 && !self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank)) {
// Mode 1 Vertical blank // Mode 1 Vertical blank
self.window_y_counter = 0;
self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank), true); self.set_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank), true);
self.set_interrupt(Interrupt::VBlank, true); interrupts.request(Interrupt::VBlank);
self.stat_interrupt(); self.stat_interrupt(interrupts);
} }
self.increment_cycles(cycles); self.increment_cycles(cycles);
@ -377,17 +364,21 @@ impl PPU {
self.reset_cycles(); self.reset_cycles();
self.lcd_y = self.lcd_y.wrapping_add(1); self.lcd_y = self.lcd_y.wrapping_add(1);
self.lcd_x = 0;
if self.window_drawn {
self.window_y_counter += 1;
}
// Frame completed // Frame completed
if self.lcd_y > 153 { if self.lcd_y > 153 {
self.window_y_counter = 0;
self.lcd_y = 0; self.lcd_y = 0;
} }
self.force_set_register(LCD_Y_ADDRESS, self.lcd_y); self.stat_interrupt(interrupts);
self.stat_interrupt();
} }
} }
fn stat_interrupt(&mut self) { fn stat_interrupt(&mut self, interrupts: &mut Interrupts) {
let prev_state = self.state; let prev_state = self.state;
let lyc_compare = self.lcd_y == self.get_register(LCD_Y_COMPARE_ADDRESS); let lyc_compare = self.lcd_y == self.get_register(LCD_Y_COMPARE_ADDRESS);
self.set_lcd_status(LCDStatus::LYCFlag, lyc_compare); self.set_lcd_status(LCDStatus::LYCFlag, lyc_compare);
@ -407,7 +398,7 @@ impl PPU {
self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank)) self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::VBlank))
); );
if self.state && !prev_state { if self.state && !prev_state {
self.set_interrupt(Interrupt::LCDSTAT, self.state); interrupts.request(Interrupt::LCDSTAT);
} }
} }
@ -421,10 +412,7 @@ impl PPU {
let long_sprites = self.get_lcd_control(LCDControl::ObjectSize); let long_sprites = self.get_lcd_control(LCDControl::ObjectSize);
let mut addr = SPRITE_ATTRIBUTE_TABLE.min().unwrap(); let mut addr = SPRITE_ATTRIBUTE_TABLE.min().unwrap();
while addr <= SPRITE_ATTRIBUTE_TABLE.max().unwrap() { while addr <= SPRITE_ATTRIBUTE_TABLE.max().unwrap() {
// The gameboy only supports 10 sprites per line,
// but since we are on an emulator we can avoud that limitation
if self.sprite_buffer.len() >= 10 { if self.sprite_buffer.len() >= 10 {
// todo!("Make a setting for the 10 sprites per scanline");
break; break;
} }
let y = self.read_oam(addr); let y = self.read_oam(addr);
@ -469,7 +457,6 @@ impl PPU {
y, y,
tile_number, tile_number,
palette_zero, palette_zero,
is_long: long_sprites,
palette: match palette_zero { palette: match palette_zero {
true => palette_0, true => palette_0,
false => palette_1, false => palette_1,
@ -485,10 +472,10 @@ impl PPU {
self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x())); self.sprite_buffer.sort_by(|a, b| a.x().cmp(&b.x()));
} }
fn find_sprite_pixel(&mut self, lcd_x: u8) -> Option<(Pixel, bool)> { fn find_sprite_pixel(&mut self) -> Option<(Pixel, bool)> {
let lcd_y = self.lcd_y; let lcd_y = self.lcd_y;
for sprite in &mut self.sprite_buffer { for sprite in &mut self.sprite_buffer {
if let Some(pixel) = sprite.get_pixel(lcd_x, lcd_y, &self.vram, self.last_bg_index) { if let Some(pixel) = sprite.get_pixel(self.lcd_x, lcd_y, &self.vram, self.last_bg_index, self.lcd_control) {
return Some(pixel); return Some(pixel);
} }
} }
@ -553,12 +540,13 @@ impl PPU {
(self.read_vram(addr), self.read_vram(addr + 1)) (self.read_vram(addr), self.read_vram(addr + 1))
} }
fn get_window_pixel(&mut self, lcd_x: u8) -> Option<Pixel> { fn get_window_pixel(&mut self) -> Option<Pixel> {
if !self.window_enable { if !self.window_enable {
return None; return None;
} }
let lcd_y = self.lcd_y; let lcd_y = self.lcd_y;
let lcd_x = self.lcd_x;
let window_x = self.window_x; let window_x = self.window_x;
let window_y = self.window_y; let window_y = self.window_y;
@ -602,11 +590,12 @@ impl PPU {
Some(PPU::get_pixel(PPU::get_palette(bit_pixel, self.bg_palette))) Some(PPU::get_pixel(PPU::get_palette(bit_pixel, self.bg_palette)))
} }
fn get_background_pixel(&mut self, lcd_x: u8) -> Option<Pixel> { fn get_background_pixel(&mut self) -> Option<Pixel> {
if !self.background_priority { if !self.background_priority {
return None; return None;
} }
let lcd_y = self.lcd_y; let lcd_y = self.lcd_y;
let lcd_x = self.lcd_x;
let y = lcd_y.wrapping_add(self.scroll_y); let y = lcd_y.wrapping_add(self.scroll_y);
let x = lcd_x.wrapping_add(self.scroll_x); let x = lcd_x.wrapping_add(self.scroll_x);
let bit_pixel_index = x.rem_euclid(8) as usize; let bit_pixel_index = x.rem_euclid(8) as usize;
@ -637,34 +626,37 @@ impl PPU {
Some(PPU::get_pixel(PPU::get_palette(bit_pixel, self.bg_palette))) Some(PPU::get_pixel(PPU::get_palette(bit_pixel, self.bg_palette)))
} }
fn draw_line(&mut self, frame_buffer: &mut [u8]) { fn draw_line(&mut self, cycles: Cycles, frame_buffer: &mut [u8]) {
let lcd_y = self.lcd_y; if self.lcd_y as u32 >= LCD_HEIGHT {
if lcd_y as u32 >= LCD_HEIGHT {
return; return;
} }
self.scroll_x = self.get_register(SCROLL_X_ADDRESS);
self.scroll_y = self.get_register(SCROLL_Y_ADDRESS);
self.window_x = self.get_register(WINDOW_X_ADDRESS);
self.window_y = self.get_register(WINDOW_Y_ADDRESS);
self.window_enable = self.get_lcd_control(LCDControl::WindowEnable);
self.background_priority = self.get_lcd_control(LCDControl::BackgroundPriority);
self.current_background_pixels = None; self.current_background_pixels = None;
self.current_window_pixels = None; self.current_window_pixels = None;
self.bg_palette = self.get_register(BACKGROUND_PALETTE_ADDRESS); self.bg_palette = self.get_register(BACKGROUND_PALETTE_ADDRESS);
let mut lcd_x: u8 = 0; let mut count = 0;
let mut window_drawn = false; while count < cycles.0 && (self.lcd_x as u32) < LCD_WIDTH {
while (lcd_x as u32) < LCD_WIDTH { let idx = (self.lcd_x as usize + (self.lcd_y as usize * LCD_WIDTH as usize)) * 4;
let idx = (lcd_x as usize + (lcd_y as usize * LCD_WIDTH as usize)) * 4;
if let Some(window_pixel) = self.get_window_pixel(lcd_x) { if let Some(window_pixel) = self.get_window_pixel() {
window_drawn = true; self.window_drawn = true;
let rgba = PPU::get_rgba(window_pixel, WINDOW_COLORS); let rgba = PPU::get_rgba(window_pixel, WINDOW_COLORS);
frame_buffer[idx] = rgba[0]; frame_buffer[idx] = rgba[0];
frame_buffer[idx + 1] = rgba[1]; frame_buffer[idx + 1] = rgba[1];
frame_buffer[idx + 2] = rgba[2]; frame_buffer[idx + 2] = rgba[2];
} else if let Some(background_pixel) = self.get_background_pixel(lcd_x) { } else if let Some(background_pixel) = self.get_background_pixel() {
let rgba = PPU::get_rgba(background_pixel, BACKGROUND_COLORS); let rgba = PPU::get_rgba(background_pixel, BACKGROUND_COLORS);
frame_buffer[idx] = rgba[0]; frame_buffer[idx] = rgba[0];
frame_buffer[idx + 1] = rgba[1]; frame_buffer[idx + 1] = rgba[1];
frame_buffer[idx + 2] = rgba[2]; frame_buffer[idx + 2] = rgba[2];
} }
if self.get_lcd_control(LCDControl::ObjectEnable) { if self.get_lcd_control(LCDControl::ObjectEnable) {
if let Some((sprite_pixel, palette_zero)) = self.find_sprite_pixel(lcd_x) { if let Some((sprite_pixel, palette_zero)) = self.find_sprite_pixel() {
let rgba = PPU::get_rgba(sprite_pixel, match palette_zero { let rgba = PPU::get_rgba(sprite_pixel, match palette_zero {
true => SPRITE_0_COLORS, true => SPRITE_0_COLORS,
false => SPRITE_1_COLORS, false => SPRITE_1_COLORS,
@ -675,10 +667,8 @@ impl PPU {
} }
} }
lcd_x += 1; self.lcd_x += 1;
} count += 1;
if window_drawn {
self.window_y_counter += 1;
} }
} }

View File

@ -1,4 +1,5 @@
use std::ops::RangeInclusive; use std::ops::RangeInclusive;
use crate::cpu::Cycles;
pub const NR10_ADDRESS: u16 = 0xFF10; pub const NR10_ADDRESS: u16 = 0xFF10;
pub const NR11_ADDRESS: u16 = 0xFF11; pub const NR11_ADDRESS: u16 = 0xFF11;
@ -33,6 +34,12 @@ pub struct Sound {
} }
impl Sound { impl Sound {
pub fn new() -> Self {
Self {
io_registers: [0; 48],
}
}
pub fn is_io_register(address: u16) -> bool { pub fn is_io_register(address: u16) -> bool {
address >= 0xFF10 && address <= 0xFF3F address >= 0xFF10 && address <= 0xFF3F
} }
@ -44,4 +51,15 @@ impl Sound {
pub fn set_register(&mut self, address: u16, data: u8) { pub fn set_register(&mut self, address: u16, data: u8) {
self.io_registers[(address - 0xFF10) as usize] = data; self.io_registers[(address - 0xFF10) as usize] = data;
} }
pub fn do_cycles(&self, cycles: Cycles) {
let mut count = 0;
while count < cycles.0 {
self.cycle();
count += 1;
}
}
fn cycle(&self) {
}
} }

View File

@ -1,4 +1,5 @@
use crate::cpu::{Cycles}; use crate::cpu::{Cycles};
use crate::interrupts::{Interrupt, Interrupts};
use crate::utils::{ use crate::utils::{
BitIndex, BitIndex,
get_bit, get_bit,
@ -13,7 +14,6 @@ pub struct Timer {
divider: u16, divider: u16,
prev_result: bool, prev_result: bool,
is_enabled: bool, is_enabled: bool,
interrupt: bool,
control: u8, control: u8,
io_registers: [u8; 4], io_registers: [u8; 4],
} }
@ -25,12 +25,23 @@ impl Timer {
divider: 0, divider: 0,
control: 0, control: 0,
prev_result: false, prev_result: false,
interrupt: false,
is_enabled: false, is_enabled: false,
io_registers: [0; 4], io_registers: [0; 4],
} }
} }
pub fn div(&self) -> u16 {
self.divider
}
pub fn set_div(&mut self, val: u16) {
self.divider = val;
}
pub fn prev_result(&self) -> bool {
self.prev_result
}
pub fn is_io_register(address: u16) -> bool { pub fn is_io_register(address: u16) -> bool {
address >= 0xFF04 && address <= 0xFF07 address >= 0xFF04 && address <= 0xFF07
} }
@ -45,40 +56,26 @@ impl Timer {
pub fn set_register(&mut self, address: u16, data: u8) { pub fn set_register(&mut self, address: u16, data: u8) {
if address == TIMER_DIVIDER_REGISTER_ADDRESS { if address == TIMER_DIVIDER_REGISTER_ADDRESS {
self.divider = 0; self.divider = 0;
self.io_registers[(TIMER_DIVIDER_REGISTER_ADDRESS - 0xFF04) as usize] = 0; return;
} else { }
self.io_registers[(address - 0xFF04) as usize] = data; self.io_registers[(address - 0xFF04) as usize] = data;
} }
}
pub fn get_interrupt(&self) -> bool {
self.interrupt
}
pub fn set_interrupt(&mut self, val: bool) {
self.interrupt = val
}
pub fn read_divider(&self) -> u8 { pub fn read_divider(&self) -> u8 {
self.divider.to_be_bytes()[0] self.divider.to_be_bytes()[0]
} }
pub fn reset(&mut self) { pub fn do_cycles(&mut self, interrupts: &mut Interrupts, cycles: Cycles) {
println!("Reset divider");
self.divider = 0;
}
pub fn do_cycles(&mut self, cycles: Cycles) {
self.is_enabled = self.is_timer_enabled(); self.is_enabled = self.is_timer_enabled();
self.control = self.get_register(TIMER_CONTROL_ADDRESS); self.control = self.get_register(TIMER_CONTROL_ADDRESS);
let mut count = 0; let mut count = 0;
while count < cycles.0 { while count < cycles.0 {
self.cycle(); self.cycle(interrupts);
count += 1; count += 1;
} }
} }
fn cycle(&mut self) { fn cycle(&mut self, interrupts: &mut Interrupts) {
self.divider = self.divider.wrapping_add(1); self.divider = self.divider.wrapping_add(1);
let result = self.is_enabled && self.get_tima_rate(); let result = self.is_enabled && self.get_tima_rate();
@ -87,7 +84,7 @@ impl Timer {
let tima = self.get_register(TIMER_COUNTER_ADDRESS).wrapping_add(1); let tima = self.get_register(TIMER_COUNTER_ADDRESS).wrapping_add(1);
if tima == 0 { if tima == 0 {
self.set_register(TIMER_COUNTER_ADDRESS, self.get_register(TIMER_MODULO_ADDRESS)); self.set_register(TIMER_COUNTER_ADDRESS, self.get_register(TIMER_MODULO_ADDRESS));
self.interrupt = true; interrupts.request(Interrupt::Timer);
} else { } else {
self.set_register(TIMER_COUNTER_ADDRESS, tima); self.set_register(TIMER_COUNTER_ADDRESS, tima);
} }
@ -111,3 +108,62 @@ impl Timer {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tima_increment() {
let mut timer = Timer::new();
let mut interrupts = Interrupts::new();
timer.set_register(TIMER_CONTROL_ADDRESS, 0b101);
timer.set_register(TIMER_COUNTER_ADDRESS, 0);
timer.set_div(0b10111);
timer.do_cycles(&mut interrupts, Cycles(1));
assert_eq!(timer.div(), 0b11000);
assert_eq!(timer.prev_result(), true);
assert_eq!(timer.get_register(TIMER_COUNTER_ADDRESS), 0);
assert_eq!(interrupts.get(Interrupt::Timer), false);
timer.do_cycles(&mut interrupts, Cycles(7));
assert_eq!(timer.div(), 0b11111);
assert_eq!(timer.prev_result(), true);
assert_eq!(timer.get_register(TIMER_COUNTER_ADDRESS), 0);
assert_eq!(interrupts.get(Interrupt::Timer), false);
timer.do_cycles(&mut interrupts, Cycles(1));
assert_eq!(timer.div(), 0b100000);
assert_eq!(timer.get_register(TIMER_COUNTER_ADDRESS), 1);
assert_eq!(timer.prev_result(), false);
assert_eq!(interrupts.get(Interrupt::Timer), false);
}
#[test]
fn test_tima_overflow() {
let mut timer = Timer::new();
let mut interrupts = Interrupts::new();
timer.set_register(TIMER_CONTROL_ADDRESS, 0b101);
timer.set_register(TIMER_COUNTER_ADDRESS, 0xFF);
timer.set_div(0b10111);
timer.do_cycles(&mut interrupts, Cycles(9));
assert_eq!(timer.div(), 0b100000);
assert_eq!(timer.get_register(TIMER_COUNTER_ADDRESS), 0x00);
assert_eq!(interrupts.get(Interrupt::Timer), true);
}
#[test]
fn test_timer_enable() {
let mut timer = Timer::new();
let mut interrupts = Interrupts::new();
timer.set_register(TIMER_CONTROL_ADDRESS, 0b101);
timer.set_register(TIMER_COUNTER_ADDRESS, 0);
timer.set_div(0b11000);
timer.do_cycles(&mut interrupts, Cycles(1));
assert_eq!(timer.div(), 0b11001);
assert_eq!(timer.get_register(TIMER_COUNTER_ADDRESS), 0);
timer.set_register(TIMER_CONTROL_ADDRESS, 0b001);
timer.do_cycles(&mut interrupts, Cycles(1));
assert_eq!(timer.div(), 0b11010);
assert_eq!(timer.get_register(TIMER_COUNTER_ADDRESS), 1);
}
}