diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..9fd17576 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ dev, master ] + pull_request: + branches: [ dev, master, feature/*, fix/* ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..0660ac1c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1587 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cast" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" +dependencies = [ + "rustc_version", +] + +[[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 = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]] +name = "cmake" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" +dependencies = [ + "cc", +] + +[[package]] +name = "core-foundation" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" + +[[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 = "criterion" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if 1.0.0", + "lazy_static", +] + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[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 = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" + +[[package]] +name = "futures-io" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" + +[[package]] +name = "futures-sink" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" + +[[package]] +name = "futures-task" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" + +[[package]] +name = "futures-util" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +dependencies = [ + "autocfg", + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gb" +version = "1.0.0" +dependencies = [ + "cfg-if 1.0.0", + "chrono", + "crossbeam-channel", + "fern", + "lib_gb", + "log", + "sdl2", + "wav", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac5956d4e63858efaec57e0d6c1c2f6a41e1487f830314a324ccd7e2223a7ca0" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + +[[package]] +name = "hyper" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[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 = [ + "criterion", + "log", + "reqwest", + "zip", +] + +[[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 = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[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 = "mio" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[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 = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "openssl" +version = "0.10.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69df2d8dfc6ce3aaf44b40dec6f487d5a886516cf6879c49e98e0710f310a058" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] +name = "plotters" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" + +[[package]] +name = "plotters-svg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "proc-macro2" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "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]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[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-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "riff" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[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 = "security-framework" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" + +[[package]] +name = "socket2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[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 = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5241dd6f21443a3606b432718b166d3cedc962fd4b8bea54a8bc7f514ebda986" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "winapi", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicode-bidi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[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 = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if 1.0.0", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "wav" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65e199c799848b4f997072aa4d673c034f80f40191f97fe2f0a23f410be1609" +dependencies = [ + "riff", +] + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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 = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + +[[package]] +name = "xattr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +dependencies = [ + "libc", +] + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "bzip2", + "crc32fast", + "flate2", + "thiserror", + "time", +] 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/LICENSE b/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index 219893b1..c8646236 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MagenBoy -A GameBoy emulator developped by me. +A GameBoy emulator developed by me. The main goal of this project is to be able to play Pokemon on my own emulator. @@ -11,18 +11,35 @@ The main goal of this project is to be able to play Pokemon on my own emulator. **More will be added if neccessary (and by neccessary I mean if games I want to play will require them)** +## How to use + +```shell +magenboy [path_to_rom] [other_optional_flags] +``` + +### Optional flags + +* `--log` - Print logs in debug mode to a file +* `--file-audio` - Saves the audio to a file +* `--full-screen` - Full screen mode +* `--no-vsync` - Disable vsync +* `--bootrom [path to bootrom file]` - Specify the path for a bootrom (If not specified the emualtor will look for `dmg_boot.bin` at the cwd) + ## GameBoy ### Development Status -- [Blargg's cpu_instrs](https://github.com/retrio/gb-test-roms/tree/master/cpu_instrs) - :thumbsup: -- [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) - :thumbsup: -- [TurtleTests](https://github.com/Powerlated/TurtleTests) - :thumbsup: -- Accurate emulation - +- CPU - Cycle accurate CPU +- PPU - Cycle accurate fifo PPU +- Timer - Mostly accurate timer +- APU - Cycle mostly accurate APU +- Tests + - [Blargg's cpu_instrs](https://github.com/retrio/gb-test-roms/tree/master/cpu_instrs) - :thumbsup: + - [dmg-acid2](https://github.com/mattcurrie/dmg-acid2) - :thumbsup: + - [TurtleTests](https://github.com/Powerlated/TurtleTests) - :thumbsup: - [CPU cycle accurate](https://github.com/retrio/gb-test-roms/tree/master/instr_timing) - :thumbsup: - - PPU currently opcoce accurate - :thumbsup: - - APU currently cycle accurate, passes some of [blargs dmg_sound tests](https://github.com/retrio/gb-test-roms/tree/master/dmg_sound)- :thumbsup: - - Timer cycle acurate, passes most of [mooneye-gb tests](https://github.com/wilbertpol/mooneye-gb/tree/master/tests/acceptance/timer) - :thumbsup: + - APU passes some of [blargs dmg_sound tests](https://github.com/retrio/gb-test-roms/tree/master/dmg_sound)- :thumbsup: + - Timer passes most of [mooneye-gb tests](https://github.com/Gekkio/mooneye-gb/tree/master/tests/acceptance/timer) - :thumbsup: ### Games Tested - Pokemon Red - :thumbsup: @@ -30,4 +47,13 @@ The main goal of this project is to be able to play Pokemon on my own emulator. ## GameBoy Color -Curerently there is no Support (support is planned in the future) \ No newline at end of file +Curerently there is no Support (support is planned in the future) + +## Resources +- [The Pandocs](https://gbdev.io/pandocs/) +- [gbops](https://izik1.github.io/gbops/index.html) +- [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) +- [The Ultimate GameBoy Talk](https://www.youtube.com/watch?v=HyzD8pNlpwI) \ No newline at end of file diff --git a/gb/Cargo.toml b/gb/Cargo.toml index f9339ee7..7eac1e63 100644 --- a/gb/Cargo.toml +++ b/gb/Cargo.toml @@ -1,14 +1,27 @@ [package] -name = "magenboy" +name = "gb" version = "1.0.0" authors = ["alloncm "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "magenboy" +path = "src/main.rs" + [dependencies] lib_gb = {path = "../lib_gb/"} log = "0.4" fern = "0.6.0" chrono = "0.4" -sdl2 = {version = "0.34", features = ["bundled","static-link"]} -wav = "0.6.0" \ No newline at end of file +sdl2 = "0.34" +wav = "1.0" +crossbeam-channel = "0.5" +cfg-if = "1.0" + +[features] +default = ["static-sdl"] +sdl-resample = [] +push-audio = [] +static-sdl = ["sdl2/bundled", "sdl2/static-link"] +static-scale = [] \ No newline at end of file diff --git a/gb/src/audio/magen_audio_resampler.rs b/gb/src/audio/magen_audio_resampler.rs new file mode 100644 index 00000000..69f868c1 --- /dev/null +++ b/gb/src/audio/magen_audio_resampler.rs @@ -0,0 +1,79 @@ +use lib_gb::apu::audio_device::{BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample}; +use super::AudioResampler; + +pub struct MagenAudioResampler{ + to_skip:u32, + sampling_buffer:Vec, + sampling_counter:u32, + reminder_steps:f32, + reminder_counter:f32, + alternate_to_skip:u32, + skip_to_use:u32, +} + +impl MagenAudioResampler{ + fn interpolate_sample(samples:&[StereoSample])->StereoSample{ + let interpulated_left_sample = samples.iter().fold(DEFAULT_SAPMPLE, |acc, x| acc + x.left_sample) / samples.len() as Sample; + let interpulated_right_sample = samples.iter().fold(DEFAULT_SAPMPLE, |acc, x| acc + x.right_sample) / samples.len() as Sample; + + return StereoSample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample}; + } +} + +impl AudioResampler for MagenAudioResampler{ + fn new(original_frequency:u32, target_frequency:u32)->Self{ + // Calling round in order to get the nearest integer and resample as precise as possible + let div = original_frequency as f32 / target_frequency as f32; + + let lower_to_skip = div.floor() as u32; + let upper_to_skip = div.ceil() as u32; + let mut reminder = div.fract(); + let (to_skip, alt_to_skip) = if reminder < 0.5{ + (lower_to_skip, upper_to_skip) + } + else{ + reminder = 1.0 - reminder; + (upper_to_skip, lower_to_skip) + }; + + if lower_to_skip == 0{ + std::panic!("target freqency is too high: {}", target_frequency); + } + + MagenAudioResampler{ + to_skip:to_skip, + sampling_buffer:Vec::with_capacity(upper_to_skip as usize), + sampling_counter: 0, + reminder_steps:reminder, + reminder_counter:0.0, + alternate_to_skip: alt_to_skip, + skip_to_use:to_skip + } + } + + fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ + let mut output = Vec::new(); + for sample in buffer.into_iter(){ + self.sampling_buffer.push(sample.clone()); + self.sampling_counter += 1; + + if self.sampling_counter == self.skip_to_use { + let interpolated_sample = Self::interpolate_sample(&self.sampling_buffer); + self.sampling_counter = 0; + self.sampling_buffer.clear(); + + output.push(interpolated_sample); + if self.reminder_counter >= 1.0{ + self.skip_to_use = self.alternate_to_skip; + self.reminder_counter -= 1.0; + } + else{ + self.skip_to_use = self.to_skip; + self.reminder_counter += self.reminder_steps; + } + } + } + + return output; + } +} \ No newline at end of file diff --git a/gb/src/audio/mod.rs b/gb/src/audio/mod.rs new file mode 100644 index 00000000..bb17c34e --- /dev/null +++ b/gb/src/audio/mod.rs @@ -0,0 +1,86 @@ +pub mod multi_device_audio; +pub mod wav_file_audio_device; +cfg_if::cfg_if!{ + if #[cfg(feature = "push-audio")]{ + pub mod sdl_push_audio_device; + pub type ChosenAudioDevice = sdl_push_audio_device::SdlPushAudioDevice; + } + else{ + pub mod sdl_pull_audio_device; + pub type ChosenAudioDevice = sdl_pull_audio_device::SdlPullAudioDevice; + } +} +cfg_if::cfg_if!{ + if #[cfg(feature = "sdl-resample")]{ + pub mod sdl_audio_resampler; + pub type ChosenResampler = sdl_audio_resampler::SdlAudioResampler; + } + else{ + pub mod magen_audio_resampler; + pub type ChosenResampler = magen_audio_resampler::MagenAudioResampler; + } +} + +use std::{ffi::CStr, mem::MaybeUninit}; +use lib_gb::apu::audio_device::{AudioDevice, BUFFER_SIZE, Sample, StereoSample}; +use sdl2::{libc::c_char, sys::*}; + +fn get_sdl_error_message()->&'static str{ + unsafe{ + let error_message:*const c_char = SDL_GetError(); + + return CStr::from_ptr(error_message).to_str().unwrap(); + } +} + +fn init_sdl_audio_device(audio_spec:&SDL_AudioSpec)->SDL_AudioDeviceID{ + let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); + + unsafe{ + SDL_Init(SDL_INIT_AUDIO); + SDL_ClearError(); + let id = SDL_OpenAudioDevice(std::ptr::null(), 0, audio_spec, uninit_audio_spec.as_mut_ptr() , 0); + + if id == 0{ + std::panic!("{}", get_sdl_error_message()); + } + + let init_audio_spec:SDL_AudioSpec = uninit_audio_spec.assume_init(); + + if init_audio_spec.freq != audio_spec.freq { + std::panic!("Error initializing audio could not use the frequency: {}", audio_spec.freq); + } + + //This will start the audio processing + SDL_PauseAudioDevice(id, 0); + return id; + } +} + +pub trait AudioResampler{ + fn new(original_frequency:u32, target_frequency:u32)->Self; + fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec; +} + +pub trait ResampledAudioDevice : AudioDevice{ + const VOLUME:Sample = 10 as Sample; + + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){ + let resample = self.get_resampler().resample(buffer); + for sample in resample{ + let(buffer, index) = self.get_audio_buffer(); + buffer[*index] = sample.left_sample * Self::VOLUME; + buffer[*index + 1] = sample.left_sample * Self::VOLUME; + *index += 2; + if *index == BUFFER_SIZE{ + *index = 0; + self.full_buffer_callback().unwrap(); + } + } + } + + fn get_audio_buffer(&mut self)->(&mut [Sample;BUFFER_SIZE], &mut usize); + fn get_resampler(&mut self)->&mut AR; + fn full_buffer_callback(&mut self)->Result<(), String>; + fn new(frequency:i32, turbo_mul:u8)->Self; +} diff --git a/gb/src/multi_device_audio.rs b/gb/src/audio/multi_device_audio.rs similarity index 84% rename from gb/src/multi_device_audio.rs rename to gb/src/audio/multi_device_audio.rs index fe169ad1..e90ba597 100644 --- a/gb/src/multi_device_audio.rs +++ b/gb/src/audio/multi_device_audio.rs @@ -11,7 +11,7 @@ impl MultiAudioDevice{ } impl AudioDevice for MultiAudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]) { + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { for device in self.devices.iter_mut(){ device.push_buffer(buffer); } diff --git a/gb/src/audio/sdl_audio_resampler.rs b/gb/src/audio/sdl_audio_resampler.rs new file mode 100644 index 00000000..00e1c339 --- /dev/null +++ b/gb/src/audio/sdl_audio_resampler.rs @@ -0,0 +1,51 @@ +use std::mem::MaybeUninit; +use lib_gb::apu::audio_device::{BUFFER_SIZE, StereoSample}; +use sdl2::sys::*; +use super::AudioResampler; + +pub struct SdlAudioResampler{ + cvt: SDL_AudioCVT +} + +impl AudioResampler for SdlAudioResampler{ + fn new(original_frequency:u32, target_frequency:u32)->Self{ + let mut cvt = unsafe{ + let mut cvt:MaybeUninit = MaybeUninit::uninit(); + SDL_BuildAudioCVT(cvt.as_mut_ptr(), AUDIO_S16 as u16, 2, original_frequency as i32, + AUDIO_S16 as u16, 2, target_frequency as i32); + cvt.assume_init() + }; + + if cvt.needed != 1{ + std::panic!("Cannot resample between freqs"); + } + + cvt.len = (BUFFER_SIZE * std::mem::size_of::()) as i32; + + log::error!("help"); + + Self{cvt} + } + + fn resample(&mut self, buffer:&[StereoSample; BUFFER_SIZE])->Vec{ + let mut buf:Vec:: = vec![0;(self.cvt.len * self.cvt.len_mult) as usize]; + + unsafe{ + std::ptr::copy_nonoverlapping(buffer.as_ptr(), buf.as_mut_ptr() as *mut StereoSample, BUFFER_SIZE); + + self.cvt.buf = buf.as_mut_ptr(); + let status_code = SDL_ConvertAudio(&mut self.cvt) != 0; + if status_code{ + std::panic!("error while converting audio, status code: {}", status_code); + } + + let buf_ptr = self.cvt.buf as *mut StereoSample; + let length = self.cvt.len_cvt as usize / std::mem::size_of::(); + let mut output = vec![StereoSample::const_defualt();length]; + + std::ptr::copy_nonoverlapping(buf_ptr, output.as_mut_ptr(), length); + + return output; + } + } +} \ No newline at end of file diff --git a/gb/src/audio/sdl_pull_audio_device.rs b/gb/src/audio/sdl_pull_audio_device.rs new file mode 100644 index 00000000..efacca24 --- /dev/null +++ b/gb/src/audio/sdl_pull_audio_device.rs @@ -0,0 +1,123 @@ +use std::ffi::c_void; +use lib_gb::{GB_FREQUENCY, apu::audio_device::*}; +use super::{AudioResampler, ResampledAudioDevice, init_sdl_audio_device}; + +use sdl2::sys::*; +use crossbeam_channel::{Receiver, Sender, bounded}; + +const BUFFERS_NUMBER:usize = 3; + +struct UserData{ + rx: Receiver, + current_buf: Option, + current_buf_index:usize, +} + +pub struct SdlPullAudioDevice{ + resampler: AR, + buffers: [[Sample;BUFFER_SIZE];BUFFERS_NUMBER], + buffer_number_index:usize, + buffer_index:usize, + + tarnsmiter: Sender, + userdata: UserData, + device_id:SDL_AudioDeviceID, +} + +impl ResampledAudioDevice for SdlPullAudioDevice{ + fn new(frequency:i32, turbo_mul:u8)->Self{ + + // cap of less than 2 hurts the fps + let(s,r) = bounded(BUFFERS_NUMBER - 1); + let data = UserData{ + current_buf:Option::None, + current_buf_index:0, + rx:r + }; + + let mut device = SdlPullAudioDevice{ + buffers:[[DEFAULT_SAPMPLE;BUFFER_SIZE];BUFFERS_NUMBER], + buffer_index:0, + buffer_number_index:0, + resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32), + tarnsmiter:s, + userdata:data, + device_id:0 + }; + + let desired_audio_spec = SDL_AudioSpec{ + freq: frequency, + format: AUDIO_S16SYS as u16, + channels: 2, + silence: 0, + samples: BUFFER_SIZE as u16, + padding: 0, + size: 0, + callback: Option::Some(audio_callback), + userdata: (&mut device.userdata) as *mut UserData as *mut c_void + }; + + // Ignore device id + device.device_id = init_sdl_audio_device(&desired_audio_spec); + + return device; + } + + fn full_buffer_callback(&mut self) ->Result<(), String> { + let result = self.tarnsmiter.send(self.buffers[self.buffer_number_index].as_ptr() as usize).map_err(|e|e.to_string()); + self.buffer_number_index = (self.buffer_number_index + 1) % BUFFERS_NUMBER; + + return result; + } + + fn get_audio_buffer(&mut self) ->(&mut [Sample;BUFFER_SIZE], &mut usize) { + (&mut self.buffers[self.buffer_number_index], &mut self.buffer_index) + } + + fn get_resampler(&mut self) ->&mut AR { + &mut self.resampler + } +} + +impl AudioDevice for SdlPullAudioDevice{ + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { + ResampledAudioDevice::push_buffer(self, buffer); + } +} + +impl Drop for SdlPullAudioDevice{ + fn drop(&mut self) { + unsafe{ + SDL_CloseAudioDevice(self.device_id); + } + } +} + +unsafe extern "C" fn audio_callback(userdata:*mut c_void, buffer:*mut u8, length:i32){ + let length = length as usize; + let safe_userdata = &mut *(userdata as *mut UserData); + + if safe_userdata.current_buf.is_none(){ + safe_userdata.current_buf = Some(safe_userdata.rx.recv().unwrap()); + } + + let samples = &*((safe_userdata.current_buf.unwrap()) as *const [Sample;BUFFER_SIZE]); + let samples_size = (samples.len() * std::mem::size_of::()) - safe_userdata.current_buf_index; + let samples_ptr = (samples.as_ptr() as *mut u8).add(safe_userdata.current_buf_index); + std::ptr::copy_nonoverlapping(samples_ptr, buffer, std::cmp::min(length, samples_size)); + + if length > samples_size && safe_userdata.rx.is_empty(){ + safe_userdata.current_buf = Option::None; + safe_userdata.current_buf_index = 0; + std::ptr::write_bytes(buffer.add(samples.len() as usize), 0, length - samples_size); + } + else if length > samples_size{ + safe_userdata.current_buf = Option::None; + safe_userdata.current_buf_index = 0; + audio_callback(userdata, buffer.add(samples_size), (length - samples_size) as i32); + } + else{ + safe_userdata.current_buf_index = length; + } +} + diff --git a/gb/src/audio/sdl_push_audio_device.rs b/gb/src/audio/sdl_push_audio_device.rs new file mode 100644 index 00000000..dbe1ec08 --- /dev/null +++ b/gb/src/audio/sdl_push_audio_device.rs @@ -0,0 +1,79 @@ +use std::{ffi::c_void, str::FromStr}; +use lib_gb::{GB_FREQUENCY, apu::audio_device::{AudioDevice, BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample}}; +use sdl2::sys::*; +use super::{AudioResampler, ResampledAudioDevice, get_sdl_error_message, init_sdl_audio_device}; + +//After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing +const BYTES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 16; + +pub struct SdlPushAudioDevice{ + device_id: SDL_AudioDeviceID, + resampler: AR, + + buffer: [Sample;BUFFER_SIZE], + buffer_index:usize, +} + +impl SdlPushAudioDevice{ + fn push_audio_to_device(&self, audio:&[Sample; BUFFER_SIZE])->Result<(),&str>{ + let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; + let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; + + unsafe{ + while SDL_GetQueuedAudioSize(self.device_id) > BYTES_TO_WAIT{ + SDL_Delay(1); + } + + SDL_ClearError(); + if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ + return Err(get_sdl_error_message()); + } + + Ok(()) + } + } +} + +impl ResampledAudioDevice for SdlPushAudioDevice{ + fn new(frequency:i32, turbo_mul:u8)->Self{ + let desired_audio_spec = SDL_AudioSpec{ + freq: frequency, + format: AUDIO_S16SYS as u16, + channels: 2, + silence: 0, + samples: BUFFER_SIZE as u16, + padding: 0, + size: 0, + callback: Option::None, + userdata: std::ptr::null_mut() + }; + + + let device_id = init_sdl_audio_device(&desired_audio_spec); + + return SdlPushAudioDevice{ + device_id: device_id, + buffer:[DEFAULT_SAPMPLE;BUFFER_SIZE], + buffer_index:0, + resampler: AudioResampler::new(GB_FREQUENCY * turbo_mul as u32, frequency as u32) + }; + } + + fn get_audio_buffer(&mut self) ->(&mut [Sample;BUFFER_SIZE], &mut usize) { + (&mut self.buffer, &mut self.buffer_index) + } + + fn get_resampler(&mut self) ->&mut AR { + &mut self.resampler + } + + fn full_buffer_callback(&mut self)->Result<(), String> { + self.push_audio_to_device(&self.buffer).map_err(|e|String::from_str(e).unwrap()) + } +} + +impl AudioDevice for SdlPushAudioDevice{ + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { + ResampledAudioDevice::push_buffer(self, buffer); + } +} \ No newline at end of file diff --git a/gb/src/wav_file_audio_device.rs b/gb/src/audio/wav_file_audio_device.rs similarity index 50% rename from gb/src/wav_file_audio_device.rs rename to gb/src/audio/wav_file_audio_device.rs index 713c581c..e2377fcc 100644 --- a/gb/src/wav_file_audio_device.rs +++ b/gb/src/audio/wav_file_audio_device.rs @@ -1,15 +1,14 @@ use lib_gb::apu::audio_device::*; +use super::AudioResampler; -use crate::audio_resampler::AudioResampler; - -pub struct WavfileAudioDevice{ +pub struct WavfileAudioDevice{ target_frequency:u32, - resampler: AudioResampler, + resampler: AR, filename:&'static str, - samples_buffer:Vec:: + samples_buffer:Vec:: } -impl WavfileAudioDevice{ +impl WavfileAudioDevice{ pub fn new(target_freq:u32, original_freq:u32, filename:&'static str)->Self{ WavfileAudioDevice{ filename, @@ -20,22 +19,22 @@ impl WavfileAudioDevice{ } } -impl AudioDevice for WavfileAudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]) { +impl AudioDevice for WavfileAudioDevice{ + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]) { self.samples_buffer.append(self.resampler.resample(buffer).as_mut()); } } -impl Drop for WavfileAudioDevice{ +impl Drop for WavfileAudioDevice{ fn drop(&mut self) { - let header = wav::header::Header::new(wav::WAV_FORMAT_IEEE_FLOAT, 2, self.target_frequency, 32); - let mut floats = Vec::with_capacity(self.samples_buffer.len() * 2); + let header = wav::header::Header::new(wav::WAV_FORMAT_PCM, 2, self.target_frequency, 16); + let mut samples = Vec::with_capacity(self.samples_buffer.len() * 2); for sample in self.samples_buffer.iter(){ - floats.push(sample.left_sample); - floats.push(sample.right_sample); + samples.push(sample.left_sample); + samples.push(sample.right_sample); } - let data = wav::BitDepth::ThirtyTwoFloat(floats); + let data = wav::BitDepth::Sixteen(samples); let mut otuput_file = std::fs::File::create(self.filename).unwrap(); wav::write(header, &data, &mut otuput_file).unwrap(); } diff --git a/gb/src/audio_resampler.rs b/gb/src/audio_resampler.rs deleted file mode 100644 index 0001d5e1..00000000 --- a/gb/src/audio_resampler.rs +++ /dev/null @@ -1,49 +0,0 @@ -use lib_gb::apu::audio_device::Sample; - -pub struct AudioResampler{ - to_skip:u32, - sampling_buffer:Vec, - sampling_counter:u32 -} - -impl AudioResampler{ - pub fn new(original_frequency:u32, target_frequency:u32)->Self{ - let to_skip = original_frequency / target_frequency as u32; - if to_skip == 0{ - std::panic!("target freqency is too high: {}", target_frequency); - } - - AudioResampler{ - to_skip:to_skip, - sampling_buffer:Vec::with_capacity(to_skip as usize), - sampling_counter: 0 - } - } - - pub fn resample(&mut self, buffer:&[Sample])->Vec{ - let mut output = Vec::new(); - for sample in buffer.into_iter(){ - self.sampling_buffer.push(*sample); - self.sampling_counter += 1; - - if self.sampling_counter == self.to_skip { - let (interpulated_left_sample, interpulated_right_sample) = Self::interpolate_sample(&self.sampling_buffer); - let interpolated_sample = Sample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample}; - self.sampling_counter = 0; - self.sampling_buffer.clear(); - - output.push(interpolated_sample); - } - } - - return output; - } - - fn interpolate_sample(samples:&[Sample])->(f32, f32){ - - let interpulated_left_sample = samples.iter().fold(0.0, |acc, x| acc + x.left_sample) / samples.len() as f32; - let interpulated_right_sample = samples.iter().fold(0.0, |acc, x| acc + x.right_sample) / samples.len() as f32; - - return (interpulated_left_sample, interpulated_right_sample); - } -} \ No newline at end of file diff --git a/gb/src/main.rs b/gb/src/main.rs index 10f0dd15..ce6dee6a 100644 --- a/gb/src/main.rs +++ b/gb/src/main.rs @@ -1,35 +1,17 @@ mod mbc_handler; mod sdl_joypad_provider; -mod sdl_audio_device; -mod audio_resampler; -mod wav_file_audio_device; -mod multi_device_audio; - -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 -}; +mod sdl_gfx_device; +mod mpmc_gfx_device; +mod audio; + +use crate::{audio::{ChosenResampler, multi_device_audio::*, ResampledAudioDevice}, mbc_handler::*, mpmc_gfx_device::MpmcGfxDevice, sdl_joypad_provider::*}; +use lib_gb::{GB_FREQUENCY, apu::audio_device::*, keypad::button::Button, machine::gameboy::GameBoy, mmu::gb_mmu::BOOT_ROM_SIZE, ppu::{gb_ppu::{BUFFERS_NUMBER, SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}}; +use std::{fs, env, result::Result, vec::Vec}; use log::info; use sdl2::sys::*; - -fn extend_vec(vec:Vec, 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:usize = 4; +const TURBO_MUL:u8 = 1; fn init_logger(debug:bool)->Result<(), fern::InitError>{ let level = if debug {log::LevelFilter::Debug} else {log::LevelFilter::Info}; @@ -37,7 +19,7 @@ fn init_logger(debug:bool)->Result<(), fern::InitError>{ .format(|out, message, record| { out.finish(format_args!( "{}[{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S.%f]"), record.level(), message )) @@ -74,8 +56,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"); @@ -85,85 +65,91 @@ 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 mut sdl_gfx_device = sdl_gfx_device::SdlGfxDevice::new("MagenBoy", SCREEN_SCALE, TURBO_MUL, + check_for_terminal_feature_flag(&args, "--no-vsync"), check_for_terminal_feature_flag(&args, "--full-screen")); + + let (s,r) = crossbeam_channel::bounded(BUFFERS_NUMBER - 1); + let mpmc_device = MpmcGfxDevice::new(s); - 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 program_name = args[1].clone(); + + let mut running = true; + // Casting to ptr cause you cant pass a raw ptr (*const/mut T) to another thread + let running_ptr:usize = (&running as *const bool) as usize; + + let emualation_thread = std::thread::Builder::new().name("Emualtion Thread".to_string()).spawn( + move || emulation_thread_main(args, program_name, mpmc_device, running_ptr) + ).unwrap(); + + unsafe{ + let mut event: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit(); + loop{ + + if SDL_PollEvent(event.as_mut_ptr()) != 0{ + let event: SDL_Event = event.assume_init(); + if event.type_ == SDL_EventType::SDL_QUIT as u32{ + break; + } + } + + let buffer = r.recv().unwrap(); + sdl_gfx_device.swap_buffer(&*(buffer as *const [u32; SCREEN_WIDTH * SCREEN_HEIGHT])); + } + + drop(r); + std::ptr::write_volatile(&mut running as *mut bool, false); + emualation_thread.join().unwrap(); + + SDL_Quit(); + } +} - let audio_device = sdl_audio_device::SdlAudioDevie::new(44100); +// Receiving usize and not raw ptr cause in rust you cant pass a raw ptr to another thread +fn emulation_thread_main(args: Vec, program_name: String, spsc_gfx_device: MpmcGfxDevice, running_ptr: usize) { + let audio_device = audio::ChosenAudioDevice::::new(44100, TURBO_MUL); + let mut devices: Vec::> = Vec::new(); devices.push(Box::new(audio_device)); if check_for_terminal_feature_flag(&args, "--file-audio"){ - let wav_ad = wav_file_audio_device::WavfileAudioDevice::new(44100, GB_FREQUENCY, "output.wav"); + let wav_ad = audio::wav_file_audio_device::WavfileAudioDevice::::new(44100, GB_FREQUENCY, "output.wav"); devices.push(Box::new(wav_ad)); + log::info!("Writing audio to file: output.wav"); } - let audio_devices = MultiAudioDevice::new(devices); - - let program_name = &args[1]; - let mut mbc = initialize_mbc(program_name); + let mut mbc = initialize_mbc(&program_name); let joypad_provider = SdlJoypadProvider::new(buttons_mapper); + let bootrom_path = if check_for_terminal_feature_flag(&args, "--bootrom"){ + let index = args.iter().position(|v| *v == String::from("--bootrom")).unwrap(); + args.get(index + 1).expect("Error! you must specify a value for the --bootrom parameter").clone() + }else{ + String::from("dmg_boot.bin") + }; - let mut gameboy = match fs::read("Dependencies\\Init\\dmg_boot.bin"){ + let mut gameboy = match fs::read(bootrom_path){ Result::Ok(file)=>{ info!("found bootrom!"); - + let mut bootrom:[u8;BOOT_ROM_SIZE] = [0;BOOT_ROM_SIZE]; for i in 0..BOOT_ROM_SIZE{ 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, spsc_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, spsc_gfx_device) } }; - info!("initialized gameboy successfully!"); unsafe{ - let mut event: std::mem::MaybeUninit = std::mem::MaybeUninit::uninit(); - loop{ - if SDL_PollEvent(event.as_mut_ptr()) != 0{ - let event: SDL_Event = event.assume_init(); - if event.type_ == SDL_EventType::SDL_QUIT as u32{ - break; - } - } - - let frame_buffer:Vec = gameboy.cycle_frame().to_vec(); - 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); + while std::ptr::read_volatile(running_ptr as *const bool){ + gameboy.cycle_frame(); } - - SDL_Quit(); } drop(gameboy); - release_mbc(program_name, mbc); -} + release_mbc(&program_name, mbc); + log::info!("released the gameboy succefully"); +} \ No newline at end of file diff --git a/gb/src/mbc_handler.rs b/gb/src/mbc_handler.rs index 879dbfa7..31619eb9 100644 --- a/gb/src/mbc_handler.rs +++ b/gb/src/mbc_handler.rs @@ -3,22 +3,17 @@ use std::boxed::Box; use std::fs; use log::info; -const CARTRIDGE_TYPE_ADDRESS:usize = 0x147; const PROGRAM_SUFFIX:&str = ".gb"; 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 mbc_type = program[CARTRIDGE_TYPE_ADDRESS]; - - info!("initializing cartridge of type: {:#X}", mbc_type); - + 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 save_data = try_get_save_data(program_name); - return lib_gb::machine::mbc_initializer::initialize_mbc(mbc_type, program, save_data); + return lib_gb::machine::mbc_initializer::initialize_mbc(program, save_data); } fn try_get_save_data(name:&String)->Option>{ diff --git a/gb/src/mpmc_gfx_device.rs b/gb/src/mpmc_gfx_device.rs new file mode 100644 index 00000000..a42aca95 --- /dev/null +++ b/gb/src/mpmc_gfx_device.rs @@ -0,0 +1,19 @@ +use lib_gb::ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}; + +pub struct MpmcGfxDevice{ + sender: crossbeam_channel::Sender +} + +impl MpmcGfxDevice{ + pub fn new(sender:crossbeam_channel::Sender)->Self{ + Self{sender} + } +} + +impl GfxDevice for MpmcGfxDevice{ + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { + if self.sender.send(buffer.as_ptr() as usize).is_err(){ + log::debug!("The receiver endpoint has been closed"); + } + } +} \ No newline at end of file diff --git a/gb/src/sdl_audio_device.rs b/gb/src/sdl_audio_device.rs deleted file mode 100644 index f2a13657..00000000 --- a/gb/src/sdl_audio_device.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::{vec::Vec,mem::MaybeUninit,ffi::{CStr, c_void}}; -use lib_gb::{GB_FREQUENCY, apu::audio_device::*}; -use sdl2::{sys::*,libc::c_char}; -use crate::audio_resampler::AudioResampler; - -//After twicking those numbers Iv reached this, this will affect fps which will affect sound tearing -const BUFFER_SIZE:usize = 1024 * 2; -const SAMPLES_TO_WAIT:u32 = BUFFER_SIZE as u32 * 4; - -pub struct SdlAudioDevie{ - device_id: SDL_AudioDeviceID, - resampler: AudioResampler, - - buffer: Vec -} - -impl SdlAudioDevie{ - pub fn new(frequency:i32)->Self{ - - let desired_audio_spec = SDL_AudioSpec{ - freq: frequency, - format: AUDIO_F32SYS as u16, - channels: 2, - silence: 0, - samples: BUFFER_SIZE as u16, - padding: 0, - size: 0, - callback: Option::None, - userdata: std::ptr::null_mut() - }; - - - let mut uninit_audio_spec:MaybeUninit = MaybeUninit::uninit(); - - let device_id = unsafe{ - SDL_ClearError(); - let id = SDL_OpenAudioDevice(std::ptr::null(), 0, &desired_audio_spec, uninit_audio_spec.as_mut_ptr() , 0); - - if id == 0{ - std::panic!("{}",Self::get_sdl_error_message()); - } - - let init_audio_spec:SDL_AudioSpec = uninit_audio_spec.assume_init(); - - if init_audio_spec.freq != frequency { - std::panic!("Error initializing audio could not use the frequency: {}", frequency); - } - - //This will start the audio processing - SDL_PauseAudioDevice(id, 0); - - id - }; - - return SdlAudioDevie{ - device_id: device_id, - buffer:Vec::with_capacity(BUFFER_SIZE), - resampler: AudioResampler::new(GB_FREQUENCY, frequency as u32) - }; - } - - fn get_sdl_error_message()->&'static str{ - unsafe{ - let error_message:*const c_char = SDL_GetError(); - - return CStr::from_ptr(error_message).to_str().unwrap(); - } - } - - - fn push_audio_to_device(&self, audio:&[f32])->Result<(),&str>{ - let audio_ptr: *const c_void = audio.as_ptr() as *const c_void; - let data_byte_len = (audio.len() * std::mem::size_of::()) as u32; - - unsafe{ - while SDL_GetQueuedAudioSize(self.device_id) > SAMPLES_TO_WAIT{} - - SDL_ClearError(); - if SDL_QueueAudio(self.device_id, audio_ptr, data_byte_len) != 0{ - return Err(Self::get_sdl_error_message()); - } - - Ok(()) - } - } -} - -impl AudioDevice for SdlAudioDevie{ - fn push_buffer(&mut self, buffer:&[Sample]){ - for sample in self.resampler.resample(buffer){ - - self.buffer.push(sample.left_sample); - self.buffer.push(sample.right_sample); - - if self.buffer.len() == BUFFER_SIZE{ - self.push_audio_to_device(&self.buffer).unwrap(); - self.buffer.clear(); - } - } - } -} \ No newline at end of file diff --git a/gb/src/sdl_gfx_device.rs b/gb/src/sdl_gfx_device.rs new file mode 100644 index 00000000..28b51a5e --- /dev/null +++ b/gb/src/sdl_gfx_device.rs @@ -0,0 +1,121 @@ +use std::ffi::{CString, c_void}; + +use lib_gb::ppu::{gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}, gfx_device::GfxDevice}; +use sdl2::sys::*; + +pub struct SdlGfxDevice{ + _window_name: CString, + renderer: *mut SDL_Renderer, + texture: *mut SDL_Texture, + discard:u8, + turbo_mul:u8, + #[cfg(feature = "static-scale")] + screen_scale:usize, +} + +impl SdlGfxDevice{ + pub fn new(window_name:&str, screen_scale: usize, turbo_mul:u8, disable_vsync:bool, full_screen:bool)->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 window_flags = if full_screen{ + #[cfg(feature = "static-scale")] + log::warn!("Please notice that this binary have been compiled with the static-scale feature and you are running with the full screen option.\nThe rendering window might be in wrong scale."); + + SDL_WindowFlags::SDL_WINDOW_FULLSCREEN_DESKTOP as u32 + } + else{ + 0 + }; + + let wind:*mut SDL_Window = SDL_CreateWindow( + cs_wnd_name.as_ptr(), + SDL_WINDOWPOS_UNDEFINED_MASK as i32, SDL_WINDOWPOS_UNDEFINED_MASK as i32, + SCREEN_WIDTH as i32 * screen_scale as i32, SCREEN_HEIGHT as i32 * screen_scale as i32, + window_flags); + + let mut render_flags = SDL_RendererFlags::SDL_RENDERER_ACCELERATED as u32; + if !disable_vsync{ + render_flags |= SDL_RendererFlags::SDL_RENDERER_PRESENTVSYNC as u32; + } + + let rend: *mut SDL_Renderer = SDL_CreateRenderer(wind, -1, render_flags); + + let texture_width:i32; + let texture_height:i32; + + cfg_if::cfg_if!{ + if #[cfg(feature = "static-scale")]{ + texture_height = SCREEN_HEIGHT as i32 * screen_scale as i32; + texture_width = SCREEN_WIDTH as i32 * screen_scale as i32; + } + else{ + if SDL_RenderSetLogicalSize(rend, (SCREEN_WIDTH as u32) as i32, (SCREEN_HEIGHT as u32) as i32) != 0{ + std::panic!("Error while setting logical rendering"); + } + texture_height = SCREEN_HEIGHT as i32; + texture_width = SCREEN_WIDTH as i32; + } + } + + let tex: *mut SDL_Texture = SDL_CreateTexture(rend, + SDL_PixelFormatEnum::SDL_PIXELFORMAT_ARGB8888 as u32, SDL_TextureAccess::SDL_TEXTUREACCESS_STREAMING as i32, + texture_width, texture_height); + + (wind, rend, tex) + }; + + Self{ + _window_name: cs_wnd_name, + renderer, + texture, + discard:0, + turbo_mul, + #[cfg(feature = "static-scale")] + screen_scale + } + } + + #[cfg(feature = "static-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(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { + self.discard = (self.discard + 1) % self.turbo_mul; + if self.discard != 0{ + return; + } + + #[cfg(feature = "static-scale")] + let buffer = Self::extend_vec(buffer, self.screen_scale, SCREEN_WIDTH, SCREEN_HEIGHT); + + unsafe{ + 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(buffer.as_ptr(),pixels as *mut u32, 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/gb/src/sdl_joypad_provider.rs b/gb/src/sdl_joypad_provider.rs index 9deaf697..b6d931d8 100644 --- a/gb/src/sdl_joypad_provider.rs +++ b/gb/src/sdl_joypad_provider.rs @@ -5,32 +5,41 @@ use lib_gb::keypad::{ button::Button }; +const PUMP_THRESHOLD:u32 = 500; + pub struct SdlJoypadProviderSDL_Scancode>{ - mapper: F + mapper: F, + pump_counter:u32, + keyborad_state: *const u8 } implSDL_Scancode> SdlJoypadProvider{ pub fn new(mapper:F)->Self{ SdlJoypadProvider{ - mapper + mapper, + pump_counter:PUMP_THRESHOLD, + keyborad_state: unsafe{SDL_GetKeyboardState(std::ptr::null_mut())} } } } implSDL_Scancode> JoypadProvider for SdlJoypadProvider{ - fn provide(&self, joypad:&mut Joypad) { + fn provide(&mut self, joypad:&mut Joypad) { let mapper = &(self.mapper); unsafe{ - let keyborad_state:*const u8 = SDL_GetKeyboardState(std::ptr::null_mut()); + self.pump_counter = (self.pump_counter + 1) % PUMP_THRESHOLD; + if self.pump_counter == 0{ + SDL_PumpEvents(); + } - joypad.buttons[Button::A as usize] = *keyborad_state.offset(mapper(Button::A) as isize) != 0; - joypad.buttons[Button::B as usize] = *keyborad_state.offset(mapper(Button::B) as isize) != 0; - joypad.buttons[Button::Start as usize] = *keyborad_state.offset(mapper(Button::Start) as isize) != 0; - joypad.buttons[Button::Select as usize] = *keyborad_state.offset(mapper(Button::Select) as isize) != 0; - joypad.buttons[Button::Up as usize] = *keyborad_state.offset(mapper(Button::Up) as isize) != 0; - joypad.buttons[Button::Down as usize] = *keyborad_state.offset(mapper(Button::Down) as isize) != 0; - joypad.buttons[Button::Right as usize] = *keyborad_state.offset(mapper(Button::Right) as isize) != 0; - joypad.buttons[Button::Left as usize] = *keyborad_state.offset(mapper(Button::Left) as isize) != 0; + joypad.buttons[Button::A as usize] = *self.keyborad_state.offset(mapper(Button::A) as isize) != 0; + joypad.buttons[Button::B as usize] = *self.keyborad_state.offset(mapper(Button::B) as isize) != 0; + joypad.buttons[Button::Start as usize] = *self.keyborad_state.offset(mapper(Button::Start) as isize) != 0; + joypad.buttons[Button::Select as usize] = *self.keyborad_state.offset(mapper(Button::Select) as isize) != 0; + joypad.buttons[Button::Up as usize] = *self.keyborad_state.offset(mapper(Button::Up) as isize) != 0; + joypad.buttons[Button::Down as usize] = *self.keyborad_state.offset(mapper(Button::Down) as isize) != 0; + joypad.buttons[Button::Right as usize] = *self.keyborad_state.offset(mapper(Button::Right) as isize) != 0; + joypad.buttons[Button::Left as usize] = *self.keyborad_state.offset(mapper(Button::Left) as isize) != 0; } } } \ No newline at end of file diff --git a/lib_gb/Cargo.toml b/lib_gb/Cargo.toml index ca607529..29631e6f 100644 --- a/lib_gb/Cargo.toml +++ b/lib_gb/Cargo.toml @@ -3,5 +3,15 @@ name = "lib_gb" version = "1.0.0" authors = ["alloncm "] edition = "2018" + [dependencies] log = "0.4" + +[dev-dependencies] +criterion = "0.3" +reqwest = { version = "0.11", features = ["blocking"] } +zip = "0.5" + +[[bench]] +name = "lib_gb_bench" +harness = false \ No newline at end of file diff --git a/lib_gb/benches/lib_gb_bench.rs b/lib_gb/benches/lib_gb_bench.rs new file mode 100644 index 00000000..0fa41513 --- /dev/null +++ b/lib_gb/benches/lib_gb_bench.rs @@ -0,0 +1,51 @@ +use criterion::*; +use lib_gb::apu::{ + audio_device::*, channel::Channel, + gb_apu::*, sound_terminal::SoundTerminal, + square_sample_producer::SquareSampleProducer +}; + +pub fn criterion_bench(c: &mut Criterion){ + struct StubApu; + impl AudioDevice for StubApu{ + fn push_buffer(&mut self, _buffer:&[StereoSample; BUFFER_SIZE]){} + } + + c.bench_function("test apu", |b| b.iter(||{ + let mut apu = GbApu::new(StubApu{}); + apu.enabled = true; + apu.sweep_tone_channel.enabled = true; + for _ in 0..100{ + apu.cycle(10); + } + })); +} + +pub fn apu_sweep_tone_channel(c: &mut Criterion){ + + c.bench_function("test square channel", |b|b.iter(||{ + let mut channel = Channel::::new(SquareSampleProducer::new_with_sweep()); + channel.sound_length = 63; + channel.enabled = true; + channel.length_enable = true; + while channel.enabled{ + let _ = channel.get_audio_sample(); + channel.update_length_register(); + } + })); +} + +pub fn apu_sound_terminal(c:&mut Criterion){ + let mut sound_terminal = SoundTerminal::default(); + for i in 0..4{ + sound_terminal.set_channel_state(i, true); + } + sound_terminal.volume = 8; + c.bench_function("Sound terminal", |b| b.iter(||{ + let samples:[Sample;4] = [100 as Sample,200 as Sample,5 as Sample,7 as Sample]; + let _ = sound_terminal.mix_terminal_samples(black_box(&samples)); + })); +} + +criterion_group!(benches, criterion_bench, apu_sweep_tone_channel, apu_sound_terminal); +criterion_main!(benches); \ No newline at end of file diff --git a/lib_gb/src/apu/apu_registers_updater.rs b/lib_gb/src/apu/apu_registers_updater.rs index 40d33124..a864b130 100644 --- a/lib_gb/src/apu/apu_registers_updater.rs +++ b/lib_gb/src/apu/apu_registers_updater.rs @@ -1,5 +1,4 @@ -use crate::{mmu::{gb_mmu::GbMmu, memory::UnprotectedMemory, io_ports::*}, utils::{bit_masks::*, memory_registers::*}}; - +use crate::{mmu::io_ports::*, utils::bit_masks::*}; use super::{ audio_device::AudioDevice, channel::Channel, @@ -14,195 +13,196 @@ use super::{ sound_utils::NUMBER_OF_CHANNELS }; -pub fn update_apu_registers(memory:&mut GbMmu, apu:&mut GbApu){ - prepare_control_registers(apu, memory); - if apu.enabled{ - prepare_wave_channel(&mut apu.wave_channel, memory, &apu.frame_sequencer); - prepare_tone_sweep_channel(&mut apu.sweep_tone_channel, memory, &apu.frame_sequencer); - prepare_noise_channel(&mut apu.noise_channel, memory, &apu.frame_sequencer); - prepare_tone_channel(&mut apu.tone_channel, memory, &apu.frame_sequencer); - } +pub fn set_nr41(channel:&mut Channel, value:u8){ + let length_data = value & 0b11_1111; + channel.sound_length = 64 - length_data as u16 } -fn prepare_tone_channel(channel:&mut Channel, memory:&mut GbMmu,fs:&FrameSequencer){ - - if memory.io_ports.get_ports_cycle_trigger()[NR21_REGISTER_INDEX as usize]{ - channel.sound_length = 64 - (memory.read_unprotected(NR21_REGISTER_ADDRESS) & 0b11_1111) as u16; - } - if memory.io_ports.get_ports_cycle_trigger()[NR22_REGISTER_INDEX as usize]{ - update_volume_envelope(memory.read_unprotected(NR22_REGISTER_ADDRESS), &mut channel.sample_producer.envelop); - - if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){ - channel.enabled = false; - } - } - if memory.io_ports.get_ports_cycle_trigger()[NR23_REGISTER_INDEX as usize]{ - //discard lower bit - channel.frequency &= 0xFF00; - channel.frequency |= memory.read_unprotected(NR23_REGISTER_ADDRESS) as u16; - } - if memory.io_ports.get_ports_cycle_trigger()[NR24_REGISTER_INDEX as usize]{ - let nr24 = memory.read_unprotected(NR24_REGISTER_ADDRESS); - //discrad upper bit - channel.frequency <<= 8; - channel.frequency >>= 8; - channel.frequency |= (nr24 as u16 & 0b111) << 8; - let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope); - update_channel_conrol_register(channel, dac_enabled, nr24, 64, fs); - if nr24 & BIT_7_MASK != 0{ - //volume - channel.sample_producer.envelop.envelop_duration_counter = channel.sample_producer.envelop.number_of_envelope_sweep; - channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume; - } +pub fn set_nr42(channel:&mut Channel, value:u8){ + update_volume_envelope( value, &mut channel.sample_producer.envelop); + if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){ + channel.enabled = false; } } -fn prepare_noise_channel(channel:&mut Channel, memory:&mut GbMmu,fs:&FrameSequencer){ +pub fn set_nr43(channel:&mut Channel, nr43:u8){ + channel.sample_producer.bits_to_shift_divisor = (nr43 & 0b1111_0000) >> 4; + channel.sample_producer.width_mode = (nr43 & BIT_3_MASK) != 0; + channel.sample_producer.divisor_code = nr43 & 0b111; +} - if memory.io_ports.get_ports_cycle_trigger()[NR41_REGISTER_INDEX as usize]{ - let length_data = memory.read_unprotected(NR41_REGISTER_ADDRESS) & 0b11_1111; - channel.sound_length = 64 - length_data as u16 - } - if memory.io_ports.get_ports_cycle_trigger()[NR42_REGISTER_INDEX as usize]{ - update_volume_envelope( memory.read_unprotected(NR42_REGISTER_ADDRESS), &mut channel.sample_producer.envelop); - if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){ - channel.enabled = false; - } - } - if memory.io_ports.get_ports_cycle_trigger()[NR43_REGISTER_INDEX as usize]{ - let nr43 = memory.read_unprotected(NR43_REGISTER_ADDRESS); - channel.sample_producer.bits_to_shift_divisor = (nr43 & 0b1111_0000) >> 4; - channel.sample_producer.width_mode = (nr43 & BIT_3_MASK) != 0; - channel.sample_producer.divisor_code = nr43 & 0b111; - } - if memory.io_ports.get_ports_cycle_trigger()[NR44_REGISTER_INDEX as usize]{ - let nr44 = memory.read_unprotected(NR44_REGISTER_ADDRESS); - let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope); - update_channel_conrol_register(channel, dac_enabled, nr44, 64, fs); - if (nr44 & BIT_7_MASK) != 0{ - //On trigger all the LFSR bits are set (lfsr is 15 bit register) - channel.sample_producer.lfsr = 0x7FFF; - - - channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume; - } +pub fn set_nr44(channel:&mut Channel, fs:&FrameSequencer, nr44:u8){ + let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope); + update_channel_conrol_register(channel, dac_enabled, nr44, 64, fs); + if (nr44 & BIT_7_MASK) != 0{ + //On trigger all the LFSR bits are set (lfsr is 15 bit register) + channel.sample_producer.lfsr = 0x7FFF; + + channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume; } } -fn prepare_control_registers(apu:&mut GbApu, memory:&impl UnprotectedMemory){ - let channel_control = memory.read_unprotected(NR50_REGISTER_ADDRESS); - apu.right_terminal.enabled = channel_control & BIT_3_MASK != 0; - apu.left_terminal.enabled = channel_control & BIT_7_MASK != 0; - - apu.right_terminal.volume = channel_control & 0b111; - apu.left_terminal.volume = (channel_control & 0b111_0000) >> 4; +pub fn set_nr50(apu:&mut GbApu, nr50:u8){ + apu.right_terminal.volume = nr50 & 0b111; + apu.left_terminal.volume = (nr50 & 0b111_0000) >> 4; +} - let channels_output_terminals = memory.read_unprotected(NR51_REGISTER_ADDRESS); +pub fn set_nr51(apu:&mut GbApu, nr51:u8){ for i in 0..NUMBER_OF_CHANNELS{ - apu.right_terminal.channels[i as usize] = channels_output_terminals & (1 << i) != 0; + apu.right_terminal.set_channel_state(i, nr51 & (1 << i) != 0); } for i in 0..NUMBER_OF_CHANNELS{ - apu.left_terminal.channels[i as usize] = channels_output_terminals & (0b1_0000 << i) != 0; + apu.left_terminal.set_channel_state(i, nr51 & (0b1_0000 << i) != 0); } - - let master_sound = memory.read_unprotected(NR52_REGISTER_ADDRESS); - apu.enabled = master_sound & BIT_7_MASK != 0; } -fn prepare_wave_channel(channel:&mut Channel, memory:&mut GbMmu,fs:&FrameSequencer){ +pub fn set_nr52(apu:&mut GbApu, ports:&mut [u8;IO_PORTS_SIZE], nr52:u8){ + let prev_apu_state = apu.enabled; + apu.enabled = nr52 & BIT_7_MASK != 0; - if memory.io_ports.get_ports_cycle_trigger()[NR30_REGISTER_INDEX as usize]{ - if (memory.read_unprotected(NR30_REGISTER_ADDRESS) & BIT_7_MASK) == 0{ - channel.enabled = false; - } - } - if memory.io_ports.get_ports_cycle_trigger()[NR31_REGISTER_INDEX as usize]{ - channel.sound_length = 256 - (memory.read_unprotected(NR31_REGISTER_ADDRESS) as u16); + // Apu turned off + if !apu.enabled && prev_apu_state{ + apu.reset(); } - if memory.io_ports.get_ports_cycle_trigger()[NR32_REGISTER_INDEX as usize]{ - //I want bits 5-6 - let nr32 = memory.read_unprotected(NR32_REGISTER_ADDRESS); - channel.sample_producer.volume = (nr32 & 0b110_0000) >> 5; + + for i in NR10_REGISTER_INDEX..NR52_REGISTER_INDEX{ + ports[i as usize] = 0; } - if memory.io_ports.get_ports_cycle_trigger()[NR33_REGISTER_INDEX as usize]{ - //discard lower 8 bits - channel.frequency &= 0xFF00; - channel.frequency |= memory.read_unprotected(NR33_REGISTER_ADDRESS) as u16; +} + +pub fn get_nr52(apu:&GbApu, nr52:&mut u8){ + set_bit_u8(nr52, 3, apu.noise_channel.enabled && apu.noise_channel.length_enable && apu.noise_channel.sound_length != 0); + set_bit_u8(nr52, 2, apu.wave_channel.enabled && apu.wave_channel.length_enable && apu.wave_channel.sound_length != 0); + set_bit_u8(nr52, 1, apu.tone_channel.enabled && apu.tone_channel.length_enable && apu.tone_channel.sound_length != 0); + set_bit_u8(nr52, 0, apu.sweep_tone_channel.enabled && apu.sweep_tone_channel.length_enable && apu.sweep_tone_channel.sound_length != 0); +} + +pub fn set_nr30(channel:&mut Channel, value:u8){ + if (value & BIT_7_MASK) == 0{ + channel.enabled = false; } - if memory.io_ports.get_ports_cycle_trigger()[NR34_REGISTER_INDEX as usize]{ - let nr34 = memory.read_unprotected(NR34_REGISTER_ADDRESS); +} - //clear the upper 8 bits - channel.frequency &= 0xFF; - channel.frequency |= ((nr34 & 0b111) as u16) << 8; +pub fn set_nr31(channel:&mut Channel, value:u8){ + channel.sound_length = 256 - (value as u16); +} - let dac_enabled = (memory.read_unprotected(NR30_REGISTER_ADDRESS) & BIT_7_MASK) != 0; - update_channel_conrol_register(channel, dac_enabled, nr34, 256, fs); +pub fn set_nr32(channel:&mut Channel, nr32:u8){ + //I want bits 5-6 + channel.sample_producer.volume = (nr32 & 0b110_0000) >> 5; +} - if nr34 & BIT_7_MASK != 0{ - channel.sample_producer.reset_counter(); - } +pub fn set_nr33(channel:&mut Channel, nr33:u8){ + //discard lower 8 bits + channel.frequency &= 0xFF00; + channel.frequency |= nr33 as u16; +} + +pub fn set_nr34(channel:&mut Channel, fs:&FrameSequencer, nr30:u8, nr34:u8){ + //clear the upper 8 bits + channel.frequency &= 0xFF; + channel.frequency |= ((nr34 & 0b111) as u16) << 8; + + let dac_enabled = (nr30 & BIT_7_MASK) != 0; + update_channel_conrol_register(channel, dac_enabled, nr34, 256, fs); + + if nr34 & BIT_7_MASK != 0{ + channel.sample_producer.reset_counter(); } +} - for i in 0..=0xF{ - channel.sample_producer.wave_samples[i] = memory.read_unprotected(0xFF30 + i as u16); +pub fn set_nr10(channel:&mut Channel, value:u8){ + let sweep = channel.sample_producer.sweep.as_mut().unwrap(); + sweep.sweep_decrease = (value & 0b1000) != 0; + sweep.sweep_shift = value & 0b111; + sweep.sweep_period = (value & 0b111_0000) >> 4; +} + +pub fn set_nr11(channel:&mut Channel, value:u8){ + channel.sample_producer.wave_duty = (value & 0b1100_0000) >> 6; + channel.sound_length = 64 - (value & 0b11_1111) as u16 +} + pub fn set_nr12(channel:&mut Channel, value:u8){ + update_volume_envelope(value, &mut channel.sample_producer.envelop); + + if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){ + channel.enabled = false; } } -fn prepare_tone_sweep_channel(channel:&mut Channel, memory:&mut GbMmu, fs:&FrameSequencer){ - let nr10 = memory.read_unprotected(NR10_REGISTER_ADDRESS); - let nr11 = memory.read_unprotected(NR11_REGISTER_ADDRESS); - let nr12 = memory.read_unprotected(NR12_REGISTER_ADDRESS); - let nr13 = memory.read_unprotected(NR13_REGISTER_ADDRESS); - let nr14 = memory.read_unprotected(NR14_REGISTER_ADDRESS); + pub fn set_nr13(channel:&mut Channel, value:u8){ + //discard lower bits + channel.frequency &= 0xFF00; + channel.frequency |= value as u16; +} + +pub fn set_nr14(channel:&mut Channel, fs:&FrameSequencer, nr14:u8){ + //discard upper bits + channel.frequency &= 0xFF; + channel.frequency |= ((nr14 & 0b111) as u16) << 8; + + let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope); + update_channel_conrol_register(channel, dac_enabled, nr14, 64, fs); - if memory.io_ports.get_ports_cycle_trigger()[NR10_REGISTER_INDEX as usize]{ + if nr14 & BIT_7_MASK != 0{ + //volume + channel.sample_producer.envelop.envelop_duration_counter = channel.sample_producer.envelop.number_of_envelope_sweep; + channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume; + //sweep let sweep = channel.sample_producer.sweep.as_mut().unwrap(); - sweep.sweep_decrease = (nr10 & 0b1000) != 0; - sweep.sweep_shift = nr10 & 0b111; - sweep.sweep_period = (nr10 & 0b111_0000) >> 4; + sweep.channel_trigger(channel.frequency); + if sweep.sweep_shift > 0{ + let freq = sweep.calculate_new_frequency(); + channel.enabled = !FreqSweep::check_overflow(freq); + } + } - if memory.io_ports.get_ports_cycle_trigger()[NR11_REGISTER_INDEX as usize]{ - channel.sample_producer.wave_duty = (nr11 & 0b1100_0000) >> 6; - channel.sound_length = 64 - (nr11 & 0b11_1111) as u16 +} + +pub fn set_nr24(channel:&mut Channel, fs:&FrameSequencer, nr14:u8){ + //discard upper bits + channel.frequency &= 0xFF; + channel.frequency |= ((nr14 & 0b111) as u16) << 8; + + let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope); + update_channel_conrol_register(channel, dac_enabled, nr14, 64, fs); + + if nr14 & BIT_7_MASK != 0{ + //volume + channel.sample_producer.envelop.envelop_duration_counter = channel.sample_producer.envelop.number_of_envelope_sweep; + channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume; } - if memory.io_ports.get_ports_cycle_trigger()[NR12_REGISTER_INDEX as usize]{ - update_volume_envelope(nr12, &mut channel.sample_producer.envelop); - - if !is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope){ - channel.enabled = false; - } +} + +pub fn set_wave_ram(wave_channel: &mut Channel, address:u16, value:u8){ + // This is not the most accurate behaviour. + // Om DMG if accessing within a small time margin after the apu accessed this ram, + // you will access the current byte the apu accessed, otherwise it will do noting. + // On other hardware it will access the current byte the apu accessed, + // currently implementing it like the original DMG on CGB will need to handle it differently. + if !wave_channel.enabled{ + wave_channel.sample_producer.wave_samples[address as usize - 0x30] = value; } - if memory.io_ports.get_ports_cycle_trigger()[NR13_REGISTER_INDEX as usize]{ - //discard lower bits - channel.frequency &= 0xFF00; - channel.frequency |= nr13 as u16; + else{ + log::warn!("Writing wave channel when its on"); } - if memory.io_ports.get_ports_cycle_trigger()[NR14_REGISTER_INDEX as usize]{ - //discard upper bits - channel.frequency &= 0xFF; - channel.frequency |= ((nr14 & 0b111) as u16) << 8; - - let dac_enabled = is_dac_enabled(channel.sample_producer.envelop.volume, channel.sample_producer.envelop.increase_envelope); - update_channel_conrol_register(channel, dac_enabled, nr14, 64, fs); - - if nr14 & BIT_7_MASK != 0{ - //volume - channel.sample_producer.envelop.envelop_duration_counter = channel.sample_producer.envelop.number_of_envelope_sweep; - channel.sample_producer.envelop.current_volume = channel.sample_producer.envelop.volume; - - //sweep - let sweep = channel.sample_producer.sweep.as_mut().unwrap(); - sweep.channel_trigger(channel.frequency); - if sweep.sweep_shift > 0{ - let freq = sweep.calculate_new_frequency(); - channel.enabled = !FreqSweep::check_overflow(freq); - } - - } +} + +pub fn get_wave_ram(wave_channel: &Channel, address:u16)->u8{ + // This is not the most accurate behaviour. + // Om DMG if accessing within a small time margin after the apu accessed this ram, + // you will access the current byte the apu accessed, otherwise it will return 0xFF. + // On other hardware it will access the current byte the apu accessed, + // currently implementing it like the original DMG on CGB will need to handle it differently. + if !wave_channel.enabled{ + wave_channel.sample_producer.wave_samples[address as usize - 0x30] + } + else{ + log::warn!("Reading wave channel when its on"); + 0xFF } } diff --git a/lib_gb/src/apu/audio_device.rs b/lib_gb/src/apu/audio_device.rs index 76a2f988..a4f4939d 100644 --- a/lib_gb/src/apu/audio_device.rs +++ b/lib_gb/src/apu/audio_device.rs @@ -1,9 +1,26 @@ -#[derive(Copy, Clone)] -pub struct Sample{ - pub left_sample:f32, - pub right_sample:f32 +pub type Sample = i16; +pub const DEFAULT_SAPMPLE:Sample = 0 as Sample; + +pub const BUFFER_SIZE:usize = 2048; + +#[repr(C, packed)] +pub struct StereoSample{ + pub left_sample:Sample, + pub right_sample:Sample +} + +impl StereoSample{ + pub const fn const_defualt()->Self{ + Self{left_sample:DEFAULT_SAPMPLE, right_sample:DEFAULT_SAPMPLE} + } +} + +impl Clone for StereoSample{ + fn clone(&self) -> Self { + Self{left_sample:self.left_sample,right_sample:self.right_sample} + } } pub trait AudioDevice{ - fn push_buffer(&mut self, buffer:&[Sample]); + fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]); } \ No newline at end of file diff --git a/lib_gb/src/apu/channel.rs b/lib_gb/src/apu/channel.rs index bc3e19e4..9d7d6a41 100644 --- a/lib_gb/src/apu/channel.rs +++ b/lib_gb/src/apu/channel.rs @@ -1,3 +1,4 @@ +use super::audio_device::{DEFAULT_SAPMPLE, Sample}; use super::sample_producer::SampleProducer; use super::timer::Timer; @@ -9,7 +10,7 @@ pub struct Channel{ pub sample_producer:Procuder, pub timer:Timer, - last_sample:u8, + last_sample:Sample, } impl Channel{ @@ -21,7 +22,7 @@ impl Channel{ length_enable:false, timer: Timer::new(sample_producer.get_updated_frequency_ticks(0)), sample_producer, - last_sample: 0 + last_sample: DEFAULT_SAPMPLE } } @@ -44,15 +45,15 @@ impl Channel{ self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency)); self.sample_producer.reset(); - self.last_sample = 0; + self.last_sample = DEFAULT_SAPMPLE; } - pub fn get_audio_sample(&mut self)->f32{ + pub fn get_audio_sample(&mut self)->Sample{ if self.enabled{ let sample = if self.timer.cycle(){ self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency)); - self.sample_producer.produce() + self.sample_producer.produce() as Sample } else{ self.last_sample @@ -60,13 +61,9 @@ impl Channel{ self.last_sample = sample; - return self.convert_digtial_to_analog(self.last_sample); + return self.last_sample; } - return 0.0; - } - - fn convert_digtial_to_analog(&self, sample:u8)->f32{ - (sample as f32 / 7.5 ) - 1.0 + return DEFAULT_SAPMPLE; } } diff --git a/lib_gb/src/apu/frame_sequencer.rs b/lib_gb/src/apu/frame_sequencer.rs index c3ca4f6d..405cd26b 100644 --- a/lib_gb/src/apu/frame_sequencer.rs +++ b/lib_gb/src/apu/frame_sequencer.rs @@ -52,7 +52,7 @@ impl FrameSequencer{ // in a rare case where the length hasnt started the second iteration there might be a length step. // This will happen only after if the channel is activated after being not active. pub fn should_next_step_clock_length(&self)->bool{ - self.counter % 2 == 0 + self.counter & 1 == 0 } pub fn reset(&mut self){ diff --git a/lib_gb/src/apu/gb_apu.rs b/lib_gb/src/apu/gb_apu.rs index 12cb1f7a..6233f458 100644 --- a/lib_gb/src/apu/gb_apu.rs +++ b/lib_gb/src/apu/gb_apu.rs @@ -9,12 +9,6 @@ use super::{ wave_sample_producer::WaveSampleProducer, sound_utils::NUMBER_OF_CHANNELS }; -use crate::{ - mmu::memory::UnprotectedMemory, - utils::{bit_masks::set_bit_u8, memory_registers::{NR10_REGISTER_ADDRESS, NR52_REGISTER_ADDRESS}} -}; - -pub const AUDIO_BUFFER_SIZE:usize = 0x400; pub struct GbApu{ pub wave_channel:Channel, @@ -26,8 +20,8 @@ pub struct GbApu{ pub left_terminal:SoundTerminal, pub enabled:bool, - audio_buffer:[Sample;AUDIO_BUFFER_SIZE], - current_t_cycle:u32, + audio_buffer:[StereoSample;BUFFER_SIZE], + current_m_cycle:u32, device:Device, last_enabled_state:bool } @@ -40,8 +34,8 @@ impl GbApu{ wave_channel:Channel::::new(WaveSampleProducer::default()), tone_channel: Channel::::new(SquareSampleProducer::new()), noise_channel: Channel::::new(NoiseSampleProducer::default()), - audio_buffer:[Sample{left_sample:0.0, right_sample:0.0}; AUDIO_BUFFER_SIZE], - current_t_cycle:0, + audio_buffer:crate::utils::create_array(StereoSample::const_defualt), + current_m_cycle:0, device:device, right_terminal: SoundTerminal::default(), left_terminal: SoundTerminal::default(), @@ -50,17 +44,14 @@ impl GbApu{ } } - pub fn cycle(&mut self, memory:&mut impl UnprotectedMemory, m_cycles_passed:u8){ - //converting m_cycles to t_cycles - let t_cycles = m_cycles_passed * 4; - + pub fn cycle(&mut self, m_cycles_passed:u8){ if self.enabled{ - for _ in 0..t_cycles{ + for _ in 0..m_cycles_passed{ let tick = self.frame_sequencer.cycle(); self.update_channels_for_frame_squencer(tick); - let mut samples:[f32;NUMBER_OF_CHANNELS] = [0.0;NUMBER_OF_CHANNELS]; + let mut samples:[Sample;NUMBER_OF_CHANNELS] = [DEFAULT_SAPMPLE ; NUMBER_OF_CHANNELS]; samples[0] = self.sweep_tone_channel.get_audio_sample(); samples[1] = self.tone_channel.get_audio_sample(); samples[2] = self.wave_channel.get_audio_sample(); @@ -69,42 +60,37 @@ impl GbApu{ let left_sample = self.left_terminal.mix_terminal_samples(&samples); let right_sample = self.right_terminal.mix_terminal_samples(&samples); - self.audio_buffer[self.current_t_cycle as usize].left_sample = left_sample; - self.audio_buffer[self.current_t_cycle as usize].right_sample = right_sample; + self.audio_buffer[self.current_m_cycle as usize].left_sample = left_sample; + self.audio_buffer[self.current_m_cycle as usize].right_sample = right_sample; - self.current_t_cycle += 1; + self.current_m_cycle += 1; self.push_buffer_if_full(); } - - self.update_registers(memory); } else{ - for _ in 0..t_cycles{ - self.audio_buffer[self.current_t_cycle as usize] = Sample{right_sample:0.0, left_sample:0.0}; - self.current_t_cycle += 1; + for _ in 0..m_cycles_passed{ + self.audio_buffer[self.current_m_cycle as usize] = StereoSample::const_defualt(); + self.current_m_cycle += 1; self.push_buffer_if_full(); } - - //Reseting the apu state - for i in NR10_REGISTER_ADDRESS..NR52_REGISTER_ADDRESS{ - memory.write_unprotected(i, 0); - } - - self.tone_channel.reset(); - self.sweep_tone_channel.reset(); - self.wave_channel.reset(); - self.noise_channel.reset(); - self.frame_sequencer.reset(); } self.last_enabled_state = self.enabled; } + pub fn reset(&mut self){ + self.tone_channel.reset(); + self.sweep_tone_channel.reset(); + self.wave_channel.reset(); + self.noise_channel.reset(); + self.frame_sequencer.reset(); + } + fn push_buffer_if_full(&mut self){ - if self.current_t_cycle as usize >= AUDIO_BUFFER_SIZE{ - self.current_t_cycle = 0; + if self.current_m_cycle as usize >= BUFFER_SIZE{ + self.current_m_cycle = 0; self.device.push_buffer(&self.audio_buffer); } } @@ -142,16 +128,6 @@ impl GbApu{ } } - fn update_registers(&mut self, memory:&mut impl UnprotectedMemory){ - - let mut control_register = memory.read_unprotected(0xFF26); - set_bit_u8(&mut control_register, 3, self.noise_channel.enabled && self.noise_channel.length_enable && self.noise_channel.sound_length != 0); - set_bit_u8(&mut control_register, 2, self.wave_channel.enabled && self.wave_channel.length_enable && self.wave_channel.sound_length != 0); - set_bit_u8(&mut control_register, 1, self.tone_channel.enabled && self.tone_channel.length_enable && self.tone_channel.sound_length != 0); - set_bit_u8(&mut control_register, 0, self.sweep_tone_channel.enabled && self.sweep_tone_channel.length_enable && self.sweep_tone_channel.sound_length != 0); - - memory.write_unprotected(NR52_REGISTER_ADDRESS, control_register); - } pub fn update_sweep_frequency(channel:&mut Channel){ let sweep:&mut FreqSweep = &mut channel.sample_producer.sweep.as_mut().unwrap(); diff --git a/lib_gb/src/apu/mod.rs b/lib_gb/src/apu/mod.rs index 8c418a4a..cf32280d 100644 --- a/lib_gb/src/apu/mod.rs +++ b/lib_gb/src/apu/mod.rs @@ -14,5 +14,5 @@ pub mod noise_sample_producer; mod sound_utils; mod apu_registers_updater; -pub use apu_registers_updater::update_apu_registers; +pub use apu_registers_updater::*; diff --git a/lib_gb/src/apu/sound_terminal.rs b/lib_gb/src/apu/sound_terminal.rs index 68b909b5..1da007bf 100644 --- a/lib_gb/src/apu/sound_terminal.rs +++ b/lib_gb/src/apu/sound_terminal.rs @@ -1,32 +1,43 @@ -use super::sound_utils::NUMBER_OF_CHANNELS; +use super::{audio_device::{DEFAULT_SAPMPLE, Sample}, sound_utils::NUMBER_OF_CHANNELS}; + +type ChannelMask = u16; + +const ENABLE_MASK:ChannelMask = 0xFFFF; +const DISABLE_MASK:ChannelMask = 0x0; pub struct SoundTerminal{ - pub enabled:bool, pub volume:u8, - pub channels:[bool;NUMBER_OF_CHANNELS] + channel_masks:[ChannelMask;NUMBER_OF_CHANNELS] } impl Default for SoundTerminal{ fn default() -> Self { SoundTerminal{ - enabled:false, - channels:[false;NUMBER_OF_CHANNELS], + channel_masks:[DISABLE_MASK;NUMBER_OF_CHANNELS], volume:0 } } } impl SoundTerminal{ - pub fn mix_terminal_samples(&self, samples:&[f32;NUMBER_OF_CHANNELS])->f32{ - let mut mixed_sample:f32 = 0.0; + pub fn set_channel_state(&mut self, channel:usize, state:bool){ + self.channel_masks[channel] = state as u16 * ENABLE_MASK; + } + + // For some reason this function is not inlined on release mode + #[inline] + pub fn mix_terminal_samples(&self, samples:&[Sample;NUMBER_OF_CHANNELS])->Sample{ + let mut mixed_sample:Sample = DEFAULT_SAPMPLE; for i in 0..NUMBER_OF_CHANNELS{ - if self.channels[i]{ - mixed_sample += samples[i]; - } + // This code should add the samples[i] only if channels[i] it true. + // After profiling this code is faster than if and since this is a hot spot in the code + // Im writing it like this. + mixed_sample += samples[i] & self.channel_masks[i] as Sample; } - mixed_sample /= NUMBER_OF_CHANNELS as f32; + mixed_sample >>= 2; // Divide by 4 in order to normal the sample - return mixed_sample * (self.volume as f32 + 1.0); + // Adding +1 cause thats how to GB calculates the sound (0 still has volume) + return mixed_sample * ((self.volume + 1) as Sample); } } \ No newline at end of file diff --git a/lib_gb/src/apu/timer.rs b/lib_gb/src/apu/timer.rs index 7604b2e2..ba05996e 100644 --- a/lib_gb/src/apu/timer.rs +++ b/lib_gb/src/apu/timer.rs @@ -3,29 +3,32 @@ pub struct Timer{ cycle_counter:u16 } +// By deviding by 4 (shifting right 2) Im losing precison in favor of performance impl Timer{ pub fn new(cycles_to_tick:u16)->Self{ Timer{ cycle_counter:0, - cycles_to_tick:cycles_to_tick + cycles_to_tick:cycles_to_tick >> 2 } } + // This function is a hot spot for the APU, almost every component uses the timer + #[inline] pub fn cycle(&mut self)->bool{ if self.cycles_to_tick != 0{ + // The calculation used to be this: + // self.cycle_counter = (self.cycle_counter + 1) % self.cycles_to_tick; + // After benching with a profiler I found that those 2 lines are much faster, probably cause there is no division here self.cycle_counter += 1; - if self.cycle_counter >= self.cycles_to_tick{ - self.cycle_counter = 0; - - return true; - } + self.cycle_counter = (self.cycle_counter != self.cycles_to_tick) as u16 * self.cycle_counter; + return self.cycle_counter == 0; } return false; } pub fn update_cycles_to_tick(&mut self, cycles_to_tick:u16){ - self.cycles_to_tick = cycles_to_tick; + self.cycles_to_tick = cycles_to_tick >> 2; self.cycle_counter = 0; } } \ No newline at end of file diff --git a/lib_gb/src/cpu/mod.rs b/lib_gb/src/cpu/mod.rs index 3f39be16..296b0e63 100644 --- a/lib_gb/src/cpu/mod.rs +++ b/lib_gb/src/cpu/mod.rs @@ -1,4 +1,5 @@ pub mod gb_cpu; pub mod register; pub mod opcodes; -pub mod flag; \ No newline at end of file +pub mod flag; +pub mod opcode_runner; \ No newline at end of file diff --git a/lib_gb/src/cpu/opcode_runner.rs b/lib_gb/src/cpu/opcode_runner.rs new file mode 100644 index 00000000..5ff6fede --- /dev/null +++ b/lib_gb/src/cpu/opcode_runner.rs @@ -0,0 +1,211 @@ +use crate::mmu::memory::Memory; +use super::{ + gb_cpu::GbCpu, + opcodes::{ + arithmetic_8bit_instructions::*, + rotate_shift_instructions::*, + cpu_control_instructions::*, + jump_instructions::*, + load_16bit_instructions::*, + arithmetic_16bit_instructions::*, + load_8bit_instructions::*, + single_bit_sintructions::*, + } +}; + + +type U16MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u16)->u8; +type U32MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u32)->u8; + +impl GbCpu{ + pub fn run_opcode(&mut self, memory:&mut impl Memory)->u8{ + let opcode = self.fetch_next_byte(memory); + + match opcode{ + //Stop + 0x10=>{ + let next_byte = self.fetch_next_byte(memory); + if next_byte == 0{ + stop(self, memory) + } + else{ + std::panic!("Invalid stop opcode, second byte: {:#X}", next_byte); + } + } + + //just cpu + 0x00=>1, + 0x07=>rlca(self), + 0x0F=>rrca(self), + 0x17=>rla(self), + 0x1F=>rra(self), + 0x2F=>cpl(self), + 0x27=>daa(self), + 0x37=>scf(self), + 0x3F=>ccf(self), + 0x76=>halt(self), + 0xE9=>jump_hl(self), + 0xF3=>di(self), + 0xF9=>load_sp_hl(self), + 0xFB=>ei(self), + + //cpu and opcode + 0x03|0x13|0x23|0x33=>inc_rr(self, opcode), + 0x04|0x14|0x24|0x0C|0x1C|0x2C|0x3C=>inc_r(self, opcode), + 0x05|0x15|0x25|0x0D|0x1D|0x2D|0x3D=>dec_r(self, opcode), + 0x09|0x19|0x29|0x39=>add_hl_rr(self, opcode), + 0x0B|0x1B|0x2B|0x3B=>dec_rr(self, opcode), + 0x40..=0x45 | 0x47..=0x4D | 0x4F..=0x55 | 0x57..=0x5D | + 0x5F..=0x65 | 0x67..=0x6D | 0x6F | 0x78..=0x7D | 0x7F=>ld_r_r(self, opcode), + 0x80..=0x85 | 0x87=>add_a_r(self, opcode), + 0x88..=0x8D | 0x8F=>adc_a_r(self, opcode), + 0x90..=0x95 | 0x97=>sub_a_r(self, opcode), + 0x98..=0x9D | 0x9F=>sbc_a_r(self, opcode), + 0xA0..=0xA5 | 0xA7=>and_a_r(self, opcode), + 0xA8..=0xAD | 0xAF=>xor_a_r(self, opcode), + 0xB0..=0xB5 | 0xB7=>or_a_r(self, opcode), + 0xB8..=0xBD | 0xBF=>cp_a_r(self, opcode), + + //u16 opcode + 0x06|0x0E|0x16|0x1E|0x26|0x2E|0x3E=>run_u16_opcode(self, memory, opcode, ld_r_n), + 0x18=>run_u16_opcode(self, memory, opcode, jump_r), + 0x20|0x28|0x30|0x38=>run_u16_opcode(self, memory, opcode, jump_r_cc), + 0xC6=>run_u16_opcode(self, memory, opcode, add_a_nn), + 0xCE=>run_u16_opcode(self, memory, opcode, adc_a_nn), + 0xD6=>run_u16_opcode(self, memory, opcode, sub_a_nn), + 0xDE=>run_u16_opcode(self, memory, opcode, sbc_a_nn), + 0xE6=>run_u16_opcode(self, memory, opcode, and_a_nn), + 0xE8=>run_u16_opcode(self, memory, opcode, add_sp_dd), + 0xEE=>run_u16_opcode(self, memory, opcode, xor_a_nn), + 0xF6=>run_u16_opcode(self, memory, opcode, or_a_nn), + 0xF8=>run_u16_opcode(self, memory, opcode, ld_hl_spdd), + 0xFE=>run_u16_opcode(self, memory, opcode, cp_a_nn), + + //u32 opcodes + 0x01 | 0x11 | 0x21 | 0x31=>run_u32_opcode(self, memory, opcode, load_rr_nn), + 0xC2 | 0xD2 | 0xCA | 0xDA=>run_u32_opcode(self, memory, opcode,jump_cc), + 0xC3=>run_u32_opcode(self, memory, opcode,jump), + + //Memory opcodes + 0x02=>ld_bc_a(self, memory), + 0x0A=>ld_a_bc(self, memory), + 0x12=>ld_de_a(self, memory), + 0x1A=>ld_a_de(self, memory), + 0x22=>ldi_hl_a(self, memory), + 0x2A=>ldi_a_hl(self, memory), + 0x32=>ldd_hl_a(self, memory), + 0x34=>inc_hl(self, memory), + 0x35=>dec_hl(self, memory), + 0x3A=>ldd_a_hl(self, memory), + 0x86=>add_a_hl(self, memory), + 0x8E=>adc_a_hl(self, memory), + 0x96=>sub_a_hl(self, memory), + 0x9E=>sbc_a_hl(self, memory), + 0xA6=>and_a_hl(self, memory), + 0xAE=>xor_a_hl(self, memory), + 0xB6=>or_a_hl(self, memory), + 0xBE=>cp_a_hl(self, memory), + 0xC9=>ret(self, memory), + 0xD9=>reti(self, memory), + 0xE2=>ld_ioport_c_a(self, memory), + 0xF2=>ld_a_ioport_c(self, memory), + + //Memory u8 opcodes + 0x46|0x4E|0x56|0x5E|0x66|0x6E|0x7E=>ld_r_hl(self, memory, opcode), + 0x70..=0x75 | 0x77=>ld_hl_r(self, memory, opcode), + 0xC0|0xC8|0xD0|0xD8=>ret_cc(self, memory, opcode), + 0xC1|0xD1|0xE1|0xF1=>pop(self, memory, opcode), + 0xC5|0xD5|0xE5|0xF5=>push(self, memory, opcode), + 0xC7|0xCF|0xD7|0xDF|0xE7|0xEF|0xF7|0xFF=>rst(self, memory, opcode), + + //Memory u16 opcodes + 0x36=>run_u16_memory_opcode(self, memory, opcode, ld_hl_n), + 0xE0=>run_u16_memory_opcode(self, memory, opcode, ld_ioport_n_a), + 0xF0=>run_u16_memory_opcode(self, memory, opcode, ld_a_ioport_n), + + //Memory u32 opcodes + + 0x08=>run_u32_memory_opcode(self, memory, opcode, ld_nn_sp), + 0xC4|0xCC|0xD4|0xDC=>run_u32_memory_opcode(self, memory, opcode, call_cc), + 0xCD=>run_u32_memory_opcode(self, memory, opcode, call), + 0xEA=>run_u32_memory_opcode(self, memory, opcode, ld_nn_a), + 0xFA=>run_u32_memory_opcode(self, memory, opcode, ld_a_nn), + + //0xCB opcodes + 0xCB=>{ + let next = self.fetch_next_byte(memory); + let u16_opcode = (opcode as u16) << 8 | next as u16; + match next{ + 0x00..=0x05 | 0x07=>rlc_r(self, u16_opcode), + 0x08..=0x0D | 0x0F=>rrc_r(self, u16_opcode), + 0x10..=0x15 | 0x17=>rl_r(self, u16_opcode), + 0x18..=0x1D | 0x1F=>rr_r(self, u16_opcode), + 0x20..=0x25 | 0x27=>sla_r(self, u16_opcode), + 0x28..=0x2D | 0x2F=>sra_r(self, u16_opcode), + 0x30..=0x35 | 0x37=>swap_r(self, u16_opcode), + 0x38..=0x3D | 0x3F=>srl_r(self, u16_opcode), + 0x40..=0x45 | 0x47..=0x4D | 0x4F..=0x55 | 0x57..=0x5D | + 0x5F..=0x65 | 0x67..=0x6D | 0x6F..=0x75 | 0x77..=0x7D | 0x7F =>bit_r(self, u16_opcode), + 0x80..=0x85 | 0x87..=0x8D | 0x8F..=0x95 | 0x97..=0x9D | + 0x9F..=0xA5 | 0xA7..=0xAD | 0xAF..=0xB5 | 0xB7..=0xBD | 0xBF =>res_r(self, u16_opcode), + 0xC0..=0xC5 | 0xC7..=0xCD | 0xCF..=0xD5 | 0xD7..=0xDD | + 0xDF..=0xE5 | 0xE7..=0xED | 0xEF..=0xF5 | 0xF7..=0xFD | 0xFF =>set_r(self, u16_opcode), + + 0x06=>rlc_hl(self, memory), + 0x0E=>rrc_hl(self, memory), + 0x16=>rl_hl(self, memory), + 0x1E=>rr_hl(self, memory), + 0x26=>sla_hl(self, memory), + 0x2E=>sra_hl(self, memory), + 0x36=>swap_hl(self, memory), + 0x3E=>srl_hl(self, memory), + + 0x46|0x4E|0x56|0x5E|0x66|0x6E|0x76|0x7E=>bit_hl(self, memory, u16_opcode), + 0x86|0x8E|0x96|0x9E|0xA6|0xAE|0xB6|0xBE=>res_hl(self, memory, u16_opcode), + 0xC6|0xCE|0xD6|0xDE|0xE6|0xEE|0xF6|0xFE=>set_hl(self, memory, u16_opcode), + } + }, + + _=>std::panic!("Unsupported opcode:{:#X}", opcode) + } + } + + + fn fetch_next_byte(&mut self, memory: &impl Memory)->u8{ + let byte:u8 = memory.read(self.program_counter); + self.program_counter+=1; + return byte; + } +} + + + +fn run_u16_opcode(cpu: &mut GbCpu, memory: &impl Memory, opcode:u8, opcode_func:fn(&mut GbCpu, u16)->u8)->u8{ + let u16_opcode = get_u16_opcode(cpu, memory, opcode); + opcode_func(cpu, u16_opcode) +} + +fn run_u16_memory_opcode(cpu: &mut GbCpu, memory: &mut T, opcode:u8, opcode_func:U16MemoryOpcodeFunc)->u8{ + let u16_opcode = get_u16_opcode(cpu, memory, opcode); + opcode_func(cpu, memory, u16_opcode) +} + +fn run_u32_opcode(cpu: &mut GbCpu, memory: &impl Memory, opcode:u8, opcode_func:fn(&mut GbCpu, u32)->u8)->u8{ + let mut u32_opcode:u32 = ((opcode as u32)<<8) | (cpu.fetch_next_byte(memory) as u32); + u32_opcode <<= 8; + u32_opcode |= cpu.fetch_next_byte(memory) as u32; + + opcode_func(cpu, u32_opcode) +} + +fn run_u32_memory_opcode(cpu: &mut GbCpu, memory: &mut T, opcode:u8, opcode_func:U32MemoryOpcodeFunc)->u8{ + let mut u32_opcode:u32 = ((opcode as u32)<<8) | (cpu.fetch_next_byte(memory) as u32); + u32_opcode <<= 8; + u32_opcode |= cpu.fetch_next_byte(memory) as u32; + + opcode_func(cpu, memory, u32_opcode) +} + +fn get_u16_opcode(cpu:&mut GbCpu, memory:&impl Memory, opcode:u8)->u16{ + (opcode as u16) << 8 | cpu.fetch_next_byte(memory) as u16 +} \ No newline at end of file diff --git a/lib_gb/src/cpu/opcodes/mod.rs b/lib_gb/src/cpu/opcodes/mod.rs index 5fafc025..1a879b56 100644 --- a/lib_gb/src/cpu/opcodes/mod.rs +++ b/lib_gb/src/cpu/opcodes/mod.rs @@ -6,6 +6,4 @@ pub mod arithmetic_16bit_instructions; pub mod jump_instructions; pub mod cpu_control_instructions; pub mod rotate_shift_instructions; -pub mod single_bit_sintructions; -pub mod opcodes_resolvers; -pub mod opcode_resolver; \ No newline at end of file +pub mod single_bit_sintructions; \ No newline at end of file diff --git a/lib_gb/src/cpu/opcodes/opcode_resolver.rs b/lib_gb/src/cpu/opcodes/opcode_resolver.rs deleted file mode 100644 index 1cd23b56..00000000 --- a/lib_gb/src/cpu/opcodes/opcode_resolver.rs +++ /dev/null @@ -1,103 +0,0 @@ -use super::opcodes_resolvers::*; -use crate::mmu::memory::Memory; - - -pub enum OpcodeFuncType{ - OpcodeFunc(OpcodeFunc), - U8OpcodeFunc(U8OpcodeFunc), - U16OpcodeFunc(U16OpcodeFunc), - U32OpcodeFunc(U32OpcodeFunc), - MemoryOpcodeFunc(MemoryOpcodeFunc), - U8MemoryOpcodeFunc(U8MemoryOpcodeFunc), - U16MemoryOpcodeFunc(U16MemoryOpcodeFunc), - U32MemoryOpcodeFunc(U32MemoryOpcodeFunc) -} - -pub struct OpcodeResolver{ - opcode_func_resolver:fn(u8)->Option, - u8_opcode_func_resolver:fn(u8)->Option, - u16_opcode_func_resolver:fn(u8,u8)->Option, - u32_opcode_func_resolver:fn(u8)->Option, - memory_opcode_func_resolver:fn(u8)->Option>, - memory_opcode_func_2bytes_resolver:fn(u8,u8)->Option>, - u8_memory_opcode_func_resolver:fn(u8)->Option>, - u16_memory_opcode_func_resolver:fn(u8,u8)->Option>, - u32_memory_opcode_func_resolver:fn(u8)->Option> -} - - -impl OpcodeResolver{ - pub fn get_opcode(&mut self, opcode:u8, memory:&impl Memory, program_counter:&mut u16)->OpcodeFuncType{ - let opcode_func = (self.opcode_func_resolver)(opcode); - match opcode_func{ - Some(func)=> return OpcodeFuncType::OpcodeFunc(func), - None=>{} - } - let memory_opcode_func = (self.memory_opcode_func_resolver)(opcode); - match memory_opcode_func{ - Some(func)=> return OpcodeFuncType::MemoryOpcodeFunc(func), - None=>{} - } - let u8_opcode_func=(self.u8_opcode_func_resolver)(opcode); - match u8_opcode_func{ - Some(func)=> return OpcodeFuncType::U8OpcodeFunc(func), - None=>{} - } - let u8_memory_func=(self.u8_memory_opcode_func_resolver)(opcode); - match u8_memory_func{ - Some(func)=> return OpcodeFuncType::U8MemoryOpcodeFunc(func), - None=>{} - } - let postfix:u8 = memory.read(*program_counter); - let u16_opcode_func=(self.u16_opcode_func_resolver)(opcode, postfix); - match u16_opcode_func{ - Some(func)=>return OpcodeFuncType::U16OpcodeFunc(func), - None=>{} - } - let u32_opcode_func = (self.u32_opcode_func_resolver)(opcode); - match u32_opcode_func{ - Some(func)=> return OpcodeFuncType::U32OpcodeFunc(func), - None=>{} - } - let u32_memory_opcode_func=(self.u32_memory_opcode_func_resolver)(opcode); - match u32_memory_opcode_func{ - Some(func)=> return OpcodeFuncType::U32MemoryOpcodeFunc(func), - None=>{} - } - let u16_memory_opcode_func = (self.u16_memory_opcode_func_resolver)(opcode, postfix); - match u16_memory_opcode_func{ - Some(func)=>return OpcodeFuncType::U16MemoryOpcodeFunc(func), - None=>{} - } - let memory_opcode_func = (self.memory_opcode_func_2bytes_resolver)(opcode, postfix); - match memory_opcode_func{ - Some(func)=>{ - //this is the only opcodes type that does not uses the postfix byte and therfore does not increment the program counter - //so im incrementing is manually - *program_counter+=1; - return OpcodeFuncType::MemoryOpcodeFunc(func); - }, - None=>{} - } - - std::panic!("no opcode matching: {:#X?}, nextb{:#X?}, c_pc{:#X?}",opcode, postfix, program_counter); - } -} - -impl Default for OpcodeResolver{ - fn default()->OpcodeResolver{ - OpcodeResolver{ - opcode_func_resolver:get_opcode_func_resolver(), - memory_opcode_func_resolver:get_memory_opcode_func_resolver(), - memory_opcode_func_2bytes_resolver:get_memory_opcode_func_2bytes_resolver(), - u8_opcode_func_resolver:get_u8_opcode_func_resolver(), - u8_memory_opcode_func_resolver:get_u8_memory_opcode_func_resolver(), - u16_memory_opcode_func_resolver:get_u16_memory_opcode_func_resolver(), - u16_opcode_func_resolver:get_u16_opcode_func_resolver(), - u32_opcode_func_resolver:get_u32_opcode_func_resolver(), - u32_memory_opcode_func_resolver:get_u32_memory_opcode_func_resolver() - } - } -} - - diff --git a/lib_gb/src/cpu/opcodes/opcodes_resolvers.rs b/lib_gb/src/cpu/opcodes/opcodes_resolvers.rs deleted file mode 100644 index 55428582..00000000 --- a/lib_gb/src/cpu/opcodes/opcodes_resolvers.rs +++ /dev/null @@ -1,216 +0,0 @@ -use crate::cpu::gb_cpu::GbCpu; -use crate::mmu::memory::Memory; -use std::option::Option; -use super::{ - arithmetic_16bit_instructions::*, - arithmetic_8bit_instructions::*, - cpu_control_instructions::*, - jump_instructions::*, - load_16bit_instructions::*, - load_8bit_instructions::*, - rotate_shift_instructions::*, - single_bit_sintructions::* -}; - - -pub type OpcodeFunc = fn(&mut GbCpu)->u8; -pub type U8OpcodeFunc = fn(&mut GbCpu,u8)->u8; -pub type U16OpcodeFunc = fn(&mut GbCpu,u16)->u8; -pub type U32OpcodeFunc = fn(&mut GbCpu,u32)->u8; -pub type MemoryOpcodeFunc = fn(&mut GbCpu,&mut T)->u8; -pub type U8MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u8)->u8; -pub type U16MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u16)->u8; -pub type U32MemoryOpcodeFunc = fn(&mut GbCpu,&mut T,u32)->u8; - - -pub fn get_opcode_func_resolver()->fn(u8)->Option{ - |opcode:u8|->Option{ - match opcode{ - 0x00=>Some(|_|1), - 0x07=>Some(rlca), - 0x0F=>Some(rrca), - 0x17=>Some(rla), - 0x1F=>Some(rra), - 0x2F=>Some(cpl), - 0x27=>Some(daa), - 0x37=>Some(scf), - 0x3F=>Some(ccf), - 0x76=>Some(halt), - 0xE9=>Some(jump_hl), - 0xF3=>Some(di), - 0xF9=>Some(load_sp_hl), - 0xFB=>Some(ei), - _=>None - } - } -} - -pub fn get_u8_opcode_func_resolver()->fn(u8)->Option{ - |opcode:u8|->Option{ - match opcode{ - 0x03|0x13|0x23|0x33=>Some(inc_rr), - 0x04|0x14|0x24|0x0C|0x1C|0x2C|0x3C=>Some(inc_r), - 0x05|0x15|0x25|0x0D|0x1D|0x2D|0x3D=>Some(dec_r), - 0x09|0x19|0x29|0x39=>Some(add_hl_rr), - 0x0B|0x1B|0x2B|0x3B=>Some(dec_rr), - 0x40..=0x45|0x47..=0x4D|0x4F..=0x55|0x57..=0x5D| - 0x5F..=0x65|0x67..=0x6D|0x6F|0x78..=0x7D|0x7F=>Some(ld_r_r), - 0x80..=0x85 | 0x87=>Some(add_a_r), - 0x88..=0x8D | 0x8F=>Some(adc_a_r), - 0x90..=0x95 | 0x97=>Some(sub_a_r), - 0x98..=0x9D | 0x9F=>Some(sbc_a_r), - 0xA0..=0xA5 | 0xA7=>Some(and_a_r), - 0xA8..=0xAD | 0xAF=>Some(xor_a_r), - 0xB0..=0xB5 | 0xB7=>Some(or_a_r), - 0xB8..=0xBD | 0xBF=>Some(cp_a_r), - _=>None - } - } -} - -pub fn get_u16_opcode_func_resolver()->fn(u8,u8)->Option{ - |opcode:u8, next:u8|->Option{ - match opcode{ - 0x06|0x0E|0x16|0x1E|0x26|0x2E|0x3E=>Some(ld_r_n), - 0x18=>Some(jump_r), - 0x20|0x28|0x30|0x38=>Some(jump_r_cc), - 0xC6=>Some(add_a_nn), - 0xCB=>match next{ - 0x00..=0x05 | 0x07=> Some(rlc_r), - 0x08..=0x0D | 0x0F=>Some(rrc_r), - 0x10..=0x15 | 0x17=>Some(rl_r), - 0x18..=0x1D | 0x1F=>Some(rr_r), - 0x20..=0x25 | 0x27=>Some(sla_r), - 0x28..=0x2D | 0x2F=>Some(sra_r), - 0x30..=0x35 | 0x37=>Some(swap_r), - 0x38..=0x3D | 0x3F=>Some(srl_r), - 0x40..=0x45 | 0x47..=0x4D | 0x4F..=0x55 | 0x57..=0x5D | - 0x5F..=0x65 | 0x67..=0x6D | 0x6F..=0x75 | 0x77..=0x7D | 0x7F =>Some(bit_r), - 0x80..=0x85 | 0x87..=0x8D | 0x8F..=0x95 | 0x97..=0x9D | - 0x9F..=0xA5 | 0xA7..=0xAD | 0xAF..=0xB5 | 0xB7..=0xBD | 0xBF =>Some(res_r), - 0xC0..=0xC5 | 0xC7..=0xCD | 0xCF..=0xD5 | 0xD7..=0xDD | - 0xDF..=0xE5 | 0xE7..=0xED | 0xEF..=0xF5 | 0xF7..=0xFD | 0xFF =>Some(set_r), - _=>None - }, - 0xCE=>Some(adc_a_nn), - 0xD6=>Some(sub_a_nn), - 0xDE=>Some(sbc_a_nn), - 0xE6=>Some(and_a_nn), - 0xE8=>Some(add_sp_dd), - 0xEE=>Some(xor_a_nn), - 0xF6=>Some(or_a_nn), - 0xF8=>Some(ld_hl_spdd), - 0xFE=>Some(cp_a_nn), - _=>None - } - } -} - -pub fn get_u32_opcode_func_resolver()->fn(u8)->Option{ - |opcode:u8|->Option{ - match opcode{ - 0x01 | 0x11 | 0x21 | 0x31=>Some(load_rr_nn), - 0xC2 | 0xD2 | 0xCA | 0xDA=>Some(jump_cc), - 0xC3=>Some(jump), - _=>None - } - } -} - -pub fn get_memory_opcode_func_resolver()->fn(u8)->Option>{ - |opcode:u8|->Option>{ - match opcode{ - 0x02=>Some(ld_bc_a), - 0x0A=>Some(ld_a_bc), - 0x12=>Some(ld_de_a), - 0x1A=>Some(ld_a_de), - 0x22=>Some(ldi_hl_a), - 0x2A=>Some(ldi_a_hl), - 0x32=>Some(ldd_hl_a), - 0x34=>Some(inc_hl), - 0x35=>Some(dec_hl), - 0x3A=>Some(ldd_a_hl), - 0x86=>Some(add_a_hl), - 0x8E=>Some(adc_a_hl), - 0x96=>Some(sub_a_hl), - 0x9E=>Some(sbc_a_hl), - 0xA6=>Some(and_a_hl), - 0xAE=>Some(xor_a_hl), - 0xB6=>Some(or_a_hl), - 0xBE=>Some(cp_a_hl), - 0xC9=>Some(ret), - 0xD9=>Some(reti), - 0xE2=>Some(ld_ioport_c_a), - 0xF2=>Some(ld_a_ioport_c), - _=>None - } - } -} - -pub fn get_memory_opcode_func_2bytes_resolver()->fn(u8,u8)->Option>{ - |opcode:u8,next_byte:u8|->Option>{ - if opcode == 0x10 && next_byte == 0{ - return Some(stop); - } - - if opcode == 0xCB{ - return match next_byte{ - 0x06=>Some(rlc_hl), - 0x0E=>Some(rrc_hl), - 0x16=>Some(rl_hl), - 0x1E=>Some(rr_hl), - 0x26=>Some(sla_hl), - 0x2E=>Some(sra_hl), - 0x36=>Some(swap_hl), - 0x3E=>Some(srl_hl), - _=>None - }; - } - - return None; - } -} - -pub fn get_u8_memory_opcode_func_resolver()->fn(u8)->Option>{ - |opcode:u8|->Option>{ - match opcode{ - 0x46|0x4E|0x56|0x5E|0x66|0x6E|0x7E=>Some(ld_r_hl), - 0x70..=0x75 | 0x77=>Some(ld_hl_r), - 0xC0|0xC8|0xD0|0xD8=>Some(ret_cc), - 0xC1|0xD1|0xE1|0xF1=>Some(pop), - 0xC5|0xD5|0xE5|0xF5=>Some(push), - 0xC7|0xCF|0xD7|0xDF|0xE7|0xEF|0xF7|0xFF=>Some(rst), - _=>None - } - } -} - -pub fn get_u16_memory_opcode_func_resolver()->fn(u8,u8)->Option>{ - |opcode:u8, next_byte:u8|->Option>{ - match opcode{ - 0x36=>Some(ld_hl_n), - 0xE0=>Some(ld_ioport_n_a), - 0xF0=>Some(ld_a_ioport_n), - 0xCB=>match next_byte{ - 0x46|0x4E|0x56|0x5E|0x66|0x6E|0x76|0x7E=>Some(bit_hl), - 0x86|0x8E|0x96|0x9E|0xA6|0xAE|0xB6|0xBE=>Some(res_hl), - 0xC6|0xCE|0xD6|0xDE|0xE6|0xEE|0xF6|0xFE=>Some(set_hl), - _=>None - }, - _=>None - } - } -} - -pub fn get_u32_memory_opcode_func_resolver()->fn(u8)->Option>{ - |opcode:u8|->Option>{ - match opcode{ - 0x08=>Some(ld_nn_sp), - 0xC4|0xCC|0xD4|0xDC=>Some(call_cc), - 0xCD=>Some(call), - 0xEA=>Some(ld_nn_a), - 0xFA=>Some(ld_a_nn), - _=>None - } - } -} \ No newline at end of file diff --git a/lib_gb/src/keypad/joypad_provider.rs b/lib_gb/src/keypad/joypad_provider.rs index 276411bc..1fe9f2e6 100644 --- a/lib_gb/src/keypad/joypad_provider.rs +++ b/lib_gb/src/keypad/joypad_provider.rs @@ -1,5 +1,5 @@ use super::joypad::Joypad; pub trait JoypadProvider{ - fn provide(&self, joypad:&mut Joypad); + fn provide(&mut self, joypad:&mut Joypad); } \ 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 eff555fb..5ed41351 100644 --- a/lib_gb/src/machine/gameboy.rs +++ b/lib_gb/src/machine/gameboy.rs @@ -1,46 +1,36 @@ use crate::{ - apu::{self, audio_device::AudioDevice, gb_apu::GbApu}, - cpu::{gb_cpu::GbCpu, opcodes::opcode_resolver::*}, + apu::{audio_device::AudioDevice, gb_apu::GbApu}, + cpu::gb_cpu::GbCpu, keypad::{joypad::Joypad, joypad_provider::JoypadProvider, joypad_register_updater}, - mmu::{carts::mbc::Mbc, gb_mmu::{GbMmu, BOOT_ROM_SIZE}, memory::Memory, mmu_register_updater, oam_dma_transferer::OamDmaTransferer}, - ppu::{gb_ppu::{CYCLES_PER_FRAME, GbPpu, SCREEN_HEIGHT, SCREEN_WIDTH}, ppu_register_updater}, timer::{gb_timer::GbTimer, timer_register_updater} + mmu::{carts::mbc::Mbc, gb_mmu::{GbMmu, BOOT_ROM_SIZE}, memory::Memory}, + 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>, - opcode_resolver:OpcodeResolver::>, - ppu:GbPpu, - apu:GbApu, + mmu: GbMmu::<'a, AD, GFX>, interrupts_handler:InterruptsHandler, - cycles_counter:u32, - joypad_provider: JP, - timer: GbTimer, - dma:OamDmaTransferer + 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), - opcode_resolver:OpcodeResolver::default(), - ppu:GbPpu::default(), - apu: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, - timer:GbTimer::default(), - dma: OamDmaTransferer::default() + 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; @@ -52,24 +42,18 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{ GameBoy{ cpu:cpu, - mmu:GbMmu::new(mbc), - opcode_resolver:OpcodeResolver::default(), - ppu:GbPpu::default(), - apu: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, - timer: GbTimer::default(), - dma: OamDmaTransferer::default() } } - 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.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); @@ -78,75 +62,24 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{ if !self.cpu.halt{ cpu_cycles_passed = self.execute_opcode(); } - - //For the DMA controller - mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma); - - timer_register_updater::update_timer_registers(&mut self.timer, &mut self.mmu.io_ports); - self.timer.cycle(&mut self.mmu, cpu_cycles_passed); - self.dma.cycle(&mut self.mmu, cpu_cycles_passed as u8); - //For the PPU - mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma); - - ppu_register_updater::update_ppu_regsiters(&mut self.mmu, &mut self.ppu); - self.ppu.update_gb_screen(&mut self.mmu, cpu_cycles_passed as u32); - mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma); + self.mmu.cycle(cpu_cycles_passed); //interrupts - let interrupt_cycles = self.interrupts_handler.handle_interrupts(&mut self.cpu, &mut self.ppu, &mut self.mmu); - if interrupt_cycles != 0{ - self.dma.cycle(&mut self.mmu, interrupt_cycles as u8); - timer_register_updater::update_timer_registers(&mut self.timer, &mut self.mmu.io_ports); - self.timer.cycle(&mut self.mmu, interrupt_cycles as u8); - mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma); - - //PPU - ppu_register_updater::update_ppu_regsiters(&mut self.mmu, &mut self.ppu); - self.ppu.update_gb_screen(&mut self.mmu, interrupt_cycles as u32); - mmu_register_updater::update_mmu_registers(&mut self.mmu, &mut self.dma); - } - - - let iter_total_cycles= cpu_cycles_passed as u32 + interrupt_cycles as u32; - - - //APU - apu::update_apu_registers(&mut self.mmu, &mut self.apu); - self.apu.cycle(&mut self.mmu, iter_total_cycles as u8); - - //clears io ports - self.mmu.io_ports.clear_io_ports_triggers(); - - //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.ppu.screen_enable{ - self.cycles_counter = 0; + let interrupt_cycles = self.interrupts_handler.handle_interrupts(&mut self.cpu, &mut self.mmu); + if interrupt_cycles != 0{ + self.mmu.cycle(interrupt_cycles); } - self.cycles_counter += iter_total_cycles; - last_ppu_power_state = self.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.ppu.get_frame_buffer(); - } - - fn fetch_next_byte(&mut self)->u8{ - let byte:u8 = self.mmu.read(self.cpu.program_counter); - self.cpu.program_counter+=1; - return byte; } fn execute_opcode(&mut self)->u8{ let pc = self.cpu.program_counter; - let opcode:u8 = self.fetch_next_byte(); //debug - if self.mmu.finished_boot{ + if self.mmu.io_components.finished_boot{ let a = *self.cpu.af.high(); let b = *self.cpu.bc.high(); let c = *self.cpu.bc.low(); @@ -159,35 +92,7 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice> GameBoy<'a, JP, AD>{ a,f,b,c,d,e,h,l, self.cpu.stack_pointer, pc, self.mmu.read(pc), self.mmu.read(pc+1), self.mmu.read(pc+2), self.mmu.read(pc+3)); } - - - let opcode_func:OpcodeFuncType = self.opcode_resolver.get_opcode(opcode, &self.mmu, &mut self.cpu.program_counter); - match opcode_func{ - OpcodeFuncType::OpcodeFunc(func)=>func(&mut self.cpu), - OpcodeFuncType::MemoryOpcodeFunc(func)=>func(&mut self.cpu, &mut self.mmu), - OpcodeFuncType::U8OpcodeFunc(func)=>func(&mut self.cpu, opcode), - OpcodeFuncType::U8MemoryOpcodeFunc(func)=>func(&mut self.cpu, &mut self.mmu, opcode), - OpcodeFuncType::U16OpcodeFunc(func)=>{ - let u16_opcode:u16 = ((opcode as u16)<<8) | (self.fetch_next_byte() as u16); - func(&mut self.cpu, u16_opcode) - }, - OpcodeFuncType::U16MemoryOpcodeFunc(func)=>{ - let u16_opcode:u16 = ((opcode as u16)<<8) | (self.fetch_next_byte() as u16); - func(&mut self.cpu, &mut self.mmu, u16_opcode) - }, - OpcodeFuncType::U32OpcodeFunc(func)=>{ - let mut u32_opcode:u32 = ((opcode as u32)<<8) | (self.fetch_next_byte() as u32); - u32_opcode <<= 8; - u32_opcode |= self.fetch_next_byte() as u32; - func(&mut self.cpu, u32_opcode) - }, - OpcodeFuncType::U32MemoryOpcodeFunc(func)=>{ - let mut u32_opcode:u32 = ((opcode as u32)<<8) | (self.fetch_next_byte() as u32); - u32_opcode <<= 8; - u32_opcode |= self.fetch_next_byte() as u32; - func(&mut self.cpu, &mut self.mmu, u32_opcode) - } - } + self.cpu.run_opcode(&mut self.mmu) } } diff --git a/lib_gb/src/machine/interrupts_handler.rs b/lib_gb/src/machine/interrupts_handler.rs index 72c7db65..89a53174 100644 --- a/lib_gb/src/machine/interrupts_handler.rs +++ b/lib_gb/src/machine/interrupts_handler.rs @@ -1,8 +1,11 @@ -use crate::{cpu::gb_cpu::GbCpu, ppu::gb_ppu::GbPpu, utils::memory_registers::IE_REGISTER_ADDRESS}; -use crate::utils::{ +use crate::{cpu::gb_cpu::GbCpu, utils::{ bit_masks::*, - memory_registers::IF_REGISTER_ADDRESS -}; + memory_registers::{ + IE_REGISTER_ADDRESS, + IF_REGISTER_ADDRESS, + STAT_REGISTER_ADDRESS + } +}}; use crate::cpu::opcodes::opcodes_utils::push; use crate::mmu::memory::Memory; @@ -26,18 +29,19 @@ impl Default for InterruptsHandler{ impl InterruptsHandler{ - pub fn handle_interrupts(&mut self, cpu:&mut GbCpu,ppu:&mut GbPpu, memory:&mut impl Memory)->u8{ + pub fn handle_interrupts(&mut self, cpu:&mut GbCpu, memory:&mut impl Memory)->u8{ //this is delayed by one instruction cause there is this delay since EI opcode is called untill the interrupt could happen let mut interupt_flag = memory.read(IF_REGISTER_ADDRESS); let interupt_enable = memory.read(IE_REGISTER_ADDRESS); + let stat_register = memory.read(STAT_REGISTER_ADDRESS); if cpu.mie && self.ei_triggered{ if interupt_flag & BIT_0_MASK != 0 && interupt_enable & BIT_0_MASK != 0{ return Self::prepare_for_interut(cpu, BIT_0_MASK, V_BLANK_INTERRUPT_ADDERESS, memory, &mut interupt_flag); } - if interupt_flag & BIT_1_MASK != 0 && interupt_enable & BIT_1_MASK != 0 && - (ppu.v_blank_interrupt_request || ppu.oam_search_interrupt_request || ppu.h_blank_interrupt_request || ppu.coincidence_interrupt_request){ + // Checking those STAT register bits for the STAT interrupts requests + if interupt_flag & BIT_1_MASK != 0 && interupt_enable & BIT_1_MASK != 0 && (stat_register & 0b111_1000) != 0{ return Self::prepare_for_interut(cpu, BIT_1_MASK, LCD_STAT_INTERRUPT_ADDERESS, memory, &mut interupt_flag); } if interupt_flag & BIT_2_MASK != 0 && interupt_enable & BIT_2_MASK != 0{ diff --git a/lib_gb/src/machine/mbc_initializer.rs b/lib_gb/src/machine/mbc_initializer.rs index d2dd9a51..22c9fce8 100644 --- a/lib_gb/src/machine/mbc_initializer.rs +++ b/lib_gb/src/machine/mbc_initializer.rs @@ -1,6 +1,11 @@ use crate::mmu::carts::*; -pub fn initialize_mbc(mbc_type:u8, program:Vec, save_data:Option>)->Box{ +const CARTRIDGE_TYPE_ADDRESS:usize = 0x147; + +pub fn initialize_mbc(program:Vec, save_data:Option>)->Box{ + let mbc_type = program[CARTRIDGE_TYPE_ADDRESS]; + log::info!("initializing cartridge of type: {:#X}", mbc_type); + match mbc_type{ 0x0|0x8=>Box::new(Rom::new(program,false, None)), 0x9=>Box::new(Rom::new(program, true, save_data)), diff --git a/lib_gb/src/mmu/gb_mmu.rs b/lib_gb/src/mmu/gb_mmu.rs index 1993a67f..8c434add 100644 --- a/lib_gb/src/mmu/gb_mmu.rs +++ b/lib_gb/src/mmu/gb_mmu.rs @@ -1,40 +1,33 @@ -use super::memory::*; -use super::ram::Ram; -use super::vram::VRam; -use super::io_ports::IoPorts; +use super::{io_components::IoComponents, memory::*}; use super::access_bus::AccessBus; -use crate::utils::memory_registers::BOOT_REGISTER_ADDRESS; +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; use std::boxed::Box; pub const BOOT_ROM_SIZE:usize = 0x100; const HRAM_SIZE:usize = 0x7F; -const SPRITE_ATTRIBUTE_TABLE_SIZE:usize = 0xA0; +const DMA_SIZE:u16 = 0xA0; +const DMA_DEST:u16 = 0xFE00; const BAD_READ_VALUE:u8 = 0xFF; -pub struct GbMmu<'a>{ - pub ram: Ram, - pub vram: VRam, - pub finished_boot:bool, - pub io_ports: IoPorts, +pub struct GbMmu<'a, D:AudioDevice, G:GfxDevice>{ + pub io_components: IoComponents, boot_rom:[u8;BOOT_ROM_SIZE], mbc: &'a mut Box, - sprite_attribute_table:[u8;SPRITE_ATTRIBUTE_TABLE_SIZE], hram: [u8;HRAM_SIZE], - interupt_enable_register:u8, - pub dma_state:Option, - pub ppu_state:PpuState + interupt_enable_register:u8 } //DMA only locks the used bus. there 2 possible used buses: extrnal (wram, rom, sram) and video (vram) -impl<'a> Memory for GbMmu<'a>{ +impl<'a, D:AudioDevice, G:GfxDevice> Memory for GbMmu<'a, D, G>{ fn read(&self, address:u16)->u8{ - if let Some (bus) = &self.dma_state{ + if let Some (bus) = &self.io_components.dma.enable{ return match address{ - 0xFF00..=0xFF7F => self.io_ports.read(address - 0xFF00), + 0xFF00..=0xFF7F => self.io_components.read(address - 0xFF00), 0xFEA0..=0xFEFF | 0xFF80..=0xFFFE | 0xFFFF=>self.read_unprotected(address), 0x8000..=0x9FFF => if let AccessBus::External = bus {self.read_unprotected(address)} else{Self::bad_dma_read(address)}, 0..=0x7FFF | 0xA000..=0xFDFF => if let AccessBus::Video = bus {self.read_unprotected(address)} else{Self::bad_dma_read(address)}, @@ -44,7 +37,7 @@ impl<'a> Memory for GbMmu<'a>{ return match address{ 0x8000..=0x9FFF=>{ if self.is_vram_ready_for_io(){ - return self.vram.read_current_bank(address-0x8000); + return self.io_components.ppu.vram.read_current_bank(address-0x8000); } else{ log::warn!("bad vram read"); @@ -53,22 +46,22 @@ impl<'a> Memory for GbMmu<'a>{ }, 0xFE00..=0xFE9F=>{ if self.is_oam_ready_for_io(){ - return self.sprite_attribute_table[(address-0xFE00) as usize]; + return self.io_components.ppu.oam[(address-0xFE00) as usize]; } else{ log::warn!("bad oam read"); return BAD_READ_VALUE; } }, - 0xFF00..=0xFF7F => self.io_ports.read(address - 0xFF00), + 0xFF00..=0xFF7F => self.io_components.read(address - 0xFF00), _=>self.read_unprotected(address) }; } fn write(&mut self, address:u16, value:u8){ - if let Some(bus) = &self.dma_state{ + if let Some(bus) = &self.io_components.dma.enable{ match address{ - 0xFF00..=0xFF7F => self.io_ports.write(address- 0xFF00, value), + 0xFF00..=0xFF7F => self.io_components.write(address- 0xFF00, value), 0xFF80..=0xFFFE | 0xFFFF=>self.write_unprotected(address, value), 0x8000..=0x9FFF => if let AccessBus::External = bus {self.write_unprotected(address, value)} else{Self::bad_dma_write(address)}, 0..=0x7FFF | 0xA000..=0xFDFF => if let AccessBus::Video = bus {self.write_unprotected(address, value)} else{Self::bad_dma_write(address)}, @@ -79,7 +72,7 @@ impl<'a> Memory for GbMmu<'a>{ match address{ 0x8000..=0x9FFF=>{ if self.is_vram_ready_for_io(){ - self.vram.write_current_bank(address-0x8000, value); + self.io_components.ppu.vram.write_current_bank(address-0x8000, value); } else{ log::warn!("bad vram write") @@ -87,24 +80,24 @@ impl<'a> Memory for GbMmu<'a>{ }, 0xFE00..=0xFE9F=>{ if self.is_oam_ready_for_io(){ - self.sprite_attribute_table[(address-0xFE00) as usize] = value; + self.io_components.ppu.oam[(address-0xFE00) as usize] = value; } else{ log::warn!("bad oam write") } }, - 0xFF00..=0xFF7F=>self.io_ports.write(address - 0xFF00, value), + 0xFF00..=0xFF7F=>self.io_components.write(address - 0xFF00, value), _=>self.write_unprotected(address, value) } } } } -impl<'a> UnprotectedMemory for GbMmu<'a>{ +impl<'a, D:AudioDevice, G:GfxDevice> UnprotectedMemory for GbMmu<'a, D, G>{ fn read_unprotected(&self, address:u16) ->u8 { return match address{ 0x0..=0xFF=>{ - if self.finished_boot{ + if self.io_components.finished_boot{ return self.mbc.read_bank0(address); } @@ -112,14 +105,14 @@ impl<'a> UnprotectedMemory for GbMmu<'a>{ }, 0x100..=0x3FFF=>self.mbc.read_bank0(address), 0x4000..=0x7FFF=>self.mbc.read_current_bank(address-0x4000), - 0x8000..=0x9FFF=>self.vram.read_current_bank(address-0x8000), + 0x8000..=0x9FFF=>self.io_components.ppu.vram.read_current_bank(address-0x8000), 0xA000..=0xBFFF=>self.mbc.read_external_ram(address-0xA000), - 0xC000..=0xCFFF =>self.ram.read_bank0(address - 0xC000), - 0xD000..=0xDFFF=>self.ram.read_current_bank(address-0xD000), - 0xE000..=0xFDFF=>self.ram.read_bank0(address - 0xE000), - 0xFE00..=0xFE9F=>self.sprite_attribute_table[(address-0xFE00) as usize], + 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.oam[(address-0xFE00) as usize], 0xFEA0..=0xFEFF=>0x0, - 0xFF00..=0xFF7F=>self.io_ports.read_unprotected(address - 0xFF00), + 0xFF00..=0xFF7F=>self.io_components.read_unprotected(address - 0xFF00), 0xFF80..=0xFFFE=>self.hram[(address-0xFF80) as usize], 0xFFFF=>self.interupt_enable_register }; @@ -128,65 +121,73 @@ impl<'a> UnprotectedMemory for GbMmu<'a>{ fn write_unprotected(&mut self, address:u16, value:u8) { match address{ 0x0..=0x7FFF=>self.mbc.write_rom(address, value), - 0x8000..=0x9FFF=>self.vram.write_current_bank(address-0x8000, value), + 0x8000..=0x9FFF=>self.io_components.ppu.vram.write_current_bank(address-0x8000, value), 0xA000..=0xBFFF=>self.mbc.write_external_ram(address-0xA000,value), - 0xC000..=0xCFFF =>self.ram.write_bank0(address - 0xC000,value), - 0xE000..=0xFDFF=>self.ram.write_bank0(address - 0xE000,value), - 0xD000..=0xDFFF=>self.ram.write_current_bank(address-0xD000,value), - 0xFE00..=0xFE9F=>self.sprite_attribute_table[(address-0xFE00) as usize] = value, + 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.oam[(address-0xFE00) as usize] = value, 0xFEA0..=0xFEFF=>{}, - 0xFF00..=0xFF7F=>self.io_ports.write_unprotected(address - 0xFF00, value), + 0xFF00..=0xFF7F=>self.io_components.write_unprotected(address - 0xFF00, value), 0xFF80..=0xFFFE=>self.hram[(address-0xFF80) as usize] = value, 0xFFFF=>self.interupt_enable_register = value } } } -impl<'a> GbMmu<'a>{ - pub fn new_with_bootrom(mbc:&'a mut Box, boot_rom:[u8;BOOT_ROM_SIZE])->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{ - ram:Ram::default(), - io_ports:IoPorts::default(), + io_components:IoComponents::new(apu, gfx_device), mbc:mbc, - vram:VRam::default(), - sprite_attribute_table:[0;SPRITE_ATTRIBUTE_TABLE_SIZE], hram:[0;HRAM_SIZE], interupt_enable_register:0, boot_rom:boot_rom, - finished_boot:false, - ppu_state:PpuState::OamSearch, - dma_state: None } } - pub fn new(mbc:&'a mut Box)->Self{ + pub fn new(mbc:&'a mut Box, apu:GbApu, gfx_device: G)->Self{ let mut mmu = GbMmu{ - ram:Ram::default(), - io_ports:IoPorts::default(), + io_components:IoComponents::new(apu, gfx_device), mbc:mbc, - vram:VRam::default(), - sprite_attribute_table:[0;SPRITE_ATTRIBUTE_TABLE_SIZE], hram:[0;HRAM_SIZE], interupt_enable_register:0, boot_rom:[0;BOOT_ROM_SIZE], - finished_boot:true, - ppu_state:PpuState::OamSearch, - dma_state:None }; //Setting the bootrom register to be set (the boot sequence has over) - mmu.io_ports.write_unprotected(BOOT_REGISTER_ADDRESS - 0xFF00, 1); + mmu.write(BOOT_REGISTER_ADDRESS, 1); mmu } + pub fn cycle(&mut self, cycles:u8){ + self.handle_dma_trasnfer(cycles); + self.io_components.cycle(cycles as u32); + } + + fn handle_dma_trasnfer(&mut self, cycles: u8) { + if self.io_components.dma.enable.is_some(){ + let cycles_to_run = std::cmp::min(self.io_components.dma.dma_cycle_counter + cycles as u16, DMA_SIZE); + for i in self.io_components.dma.dma_cycle_counter..cycles_to_run as u16{ + self.write_unprotected(DMA_DEST + i, self.read_unprotected(self.io_components.dma.soure_address + i)); + } + + self.io_components.dma.dma_cycle_counter += cycles as u16; + if self.io_components.dma.dma_cycle_counter >= DMA_SIZE{ + self.io_components.dma.dma_cycle_counter = 0; + self.io_components.dma.enable = Option::None; + } + } + } + fn is_oam_ready_for_io(&self)->bool{ - let ppu_state = self.ppu_state as u8; + let ppu_state = self.io_components.ppu.state as u8; return ppu_state != PpuState::OamSearch as u8 && ppu_state != PpuState::PixelTransfer as u8 } fn is_vram_ready_for_io(&self)->bool{ - return self.ppu_state as u8 != PpuState::PixelTransfer as u8; + return self.io_components.ppu.state as u8 != PpuState::PixelTransfer as u8; } fn bad_dma_read(address:u16)->u8{ diff --git a/lib_gb/src/mmu/io_components.rs b/lib_gb/src/mmu/io_components.rs new file mode 100644 index 00000000..fc18efc4 --- /dev/null +++ b/lib_gb/src/mmu/io_components.rs @@ -0,0 +1,176 @@ +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::*; + +pub const IO_PORTS_SIZE:usize = 0x80; +const WAVE_RAM_START_INDEX:u16 = 0x30; +const WAVE_RAM_END_INDEX:u16 = 0x3F; + +pub struct IoComponents{ + pub ram: Ram, + pub apu: GbApu, + pub timer: GbTimer, + pub ppu:GbPpu, + ports:[u8;IO_PORTS_SIZE], + pub dma:OamDmaTransfer, + pub finished_boot:bool, +} + +io_port_index!(LCDC_REGISTER_INDEX, LCDC_REGISTER_ADDRESS); +io_port_index!(STAT_REGISTER_INDEX, STAT_REGISTER_ADDRESS); +io_port_index!(SCY_REGISTER_INDEX, SCY_REGISTER_ADDRESS); +io_port_index!(SCX_REGISTER_INDEX, SCX_REGISTER_ADDRESS); +io_port_index!(LY_REGISTER_INDEX, LY_REGISTER_ADDRESS); +io_port_index!(LYC_REGISTER_INDEX, LYC_REGISTER_ADDRESS); +io_port_index!(DMA_REGISTER_INDEX, DMA_REGISTER_ADDRESS); +io_port_index!(WY_REGISTER_INDEX, WY_REGISTER_ADDRESS); +io_port_index!(WX_REGISTER_INDEX, WX_REGISTER_ADDRESS); +io_port_index!(BOOT_REGISTER_INDEX, BOOT_REGISTER_ADDRESS); +io_port_index!(BGP_REGISTER_INDEX, BGP_REGISTER_ADDRESS); +io_port_index!(OBP0_REGISTER_INDEX, OBP0_REGISTER_ADDRESS); +io_port_index!(OBP1_REGISTER_INDEX, OBP1_REGISTER_ADDRESS); +io_port_index!(IF_REGISTER_INDEX, IF_REGISTER_ADDRESS); + +impl Memory for IoComponents{ + fn read(&self, address:u16)->u8 { + let mut value = self.ports[address as usize]; + return match address { + //Timer + TAC_REGISTER_INDEX=> value & 0b111, + DIV_REGISTER_INDEX=> get_div(&self.timer), + TIMA_REGISTER_INDEX=> self.timer.tima_register, + //APU + NR10_REGISTER_INDEX=>value | 0b1000_0000, + NR11_REGISTER_INDEX=> value | 0b0011_1111, + NR13_REGISTER_INDEX=> 0xFF, + NR14_REGISTER_INDEX=> value | 0b1011_1111, + 0x15 => 0xFF, //Not used + NR21_REGISTER_INDEX=> value | 0b0011_1111, + NR23_REGISTER_INDEX=> 0xFF, + NR24_REGISTER_INDEX=> value | 0b1011_1111, + NR30_REGISTER_INDEX=> value | 0b0111_1111, + NR31_REGISTER_INDEX=> value | 0xFF, + NR32_REGISTER_INDEX=> value | 0b1001_1111, + NR33_REGISTER_INDEX=> value | 0xFF, + NR34_REGISTER_INDEX=> value | 0b1011_1111, + 0x1F => 0xFF, //Not used + NR41_REGISTER_INDEX=> 0xFF, + NR44_REGISTER_INDEX=> value | 0b1011_1111, + NR52_REGISTER_INDEX=> { + get_nr52(&self.apu, &mut value); + value + } + 0x27..=0x2F => 0xFF, //Not used + WAVE_RAM_START_INDEX..=WAVE_RAM_END_INDEX => get_wave_ram(&self.apu.wave_channel, address), + //PPU + STAT_REGISTER_INDEX=> get_stat(&self.ppu), + LY_REGISTER_INDEX=> get_ly(&self.ppu), + //Joypad + JOYP_REGISTER_INDEX => { + let joypad_value = self.ports[JOYP_REGISTER_INDEX as usize]; + (joypad_value & 0xF) | (value & 0xF0) + } + _=>value + }; + } + + fn write(&mut self, address:u16, mut value:u8) { + match address{ + //timer + DIV_REGISTER_INDEX=> { + reset_div(&mut self.timer); + value = 0; + } + TIMA_REGISTER_INDEX=> set_tima(&mut self.timer, value), + TMA_REGISTER_INDEX=> set_tma(&mut self.timer, value), + TAC_REGISTER_INDEX=> { + set_tac(&mut self.timer, value); + value &= 0b111; + } + //APU + NR10_REGISTER_INDEX=> set_nr10(&mut self.apu.sweep_tone_channel, value), + NR11_REGISTER_INDEX=> set_nr11(&mut self.apu.sweep_tone_channel, value), + NR12_REGISTER_INDEX=> set_nr12(&mut self.apu.sweep_tone_channel, value), + NR13_REGISTER_INDEX=> set_nr13(&mut self.apu.sweep_tone_channel, value), + NR14_REGISTER_INDEX=> set_nr14(&mut self.apu.sweep_tone_channel, &self.apu.frame_sequencer, value), + NR21_REGISTER_INDEX=> set_nr11(&mut self.apu.tone_channel, value), + NR22_REGISTER_INDEX=> set_nr12(&mut self.apu.tone_channel, value), + NR23_REGISTER_INDEX=> set_nr13(&mut self.apu.tone_channel, value), + NR24_REGISTER_INDEX=> set_nr24(&mut self.apu.tone_channel, &self.apu.frame_sequencer, value), + NR30_REGISTER_INDEX=> set_nr30(&mut self.apu.wave_channel, value), + NR31_REGISTER_INDEX=> set_nr31(&mut self.apu.wave_channel, value), + NR32_REGISTER_INDEX=> set_nr32(&mut self.apu.wave_channel, value), + NR33_REGISTER_INDEX=> set_nr33(&mut self.apu.wave_channel, value), + NR34_REGISTER_INDEX=> set_nr34(&mut self.apu.wave_channel, &self.apu.frame_sequencer, self.ports[NR30_REGISTER_INDEX as usize],value), + NR41_REGISTER_INDEX=> set_nr41(&mut self.apu.noise_channel, value), + NR42_REGISTER_INDEX=> set_nr42(&mut self.apu.noise_channel, value), + NR43_REGISTER_INDEX=> set_nr43(&mut self.apu.noise_channel, value), + NR44_REGISTER_INDEX=> set_nr44(&mut self.apu.noise_channel, &self.apu.frame_sequencer, value), + NR50_REGISTER_INDEX=> set_nr50(&mut self.apu, value), + NR51_REGISTER_INDEX=> set_nr51(&mut self.apu, value), + NR52_REGISTER_INDEX=> set_nr52(&mut self.apu, &mut self.ports,value), + WAVE_RAM_START_INDEX..=WAVE_RAM_END_INDEX => set_wave_ram(&mut self.apu.wave_channel, address, value), + //PPU + LCDC_REGISTER_INDEX=> handle_lcdcontrol_register(value, &mut self.ppu), + STAT_REGISTER_INDEX=> { + update_stat_register(value, &mut self.ppu); + value = (value >> 2) << 2; + }, + SCY_REGISTER_INDEX=> set_scy(&mut self.ppu, value), + SCX_REGISTER_INDEX=> set_scx(&mut self.ppu, value), + LYC_REGISTER_INDEX=> set_lyc(&mut self.ppu, value), + DMA_REGISTER_INDEX=>{ + let address = (value as u16) << 8; + self.dma.soure_address = address; + self.dma.enable = match value{ + 0..=0x7F=>Some(AccessBus::External), + 0x80..=0x9F=>Some(AccessBus::Video), + 0xA0..=0xFF=>Some(AccessBus::External) + } + } + BGP_REGISTER_INDEX=> handle_bg_pallet_register(value,&mut self.ppu.bg_color_mapping), + OBP0_REGISTER_INDEX=> handle_obp_pallet_register(value,&mut self.ppu.obj_color_mapping0), + OBP1_REGISTER_INDEX=> handle_obp_pallet_register(value,&mut self.ppu.obj_color_mapping1), + WY_REGISTER_INDEX=> handle_wy_register(value, &mut self.ppu), + WX_REGISTER_INDEX=> handle_wx_register(value, &mut self.ppu), + BOOT_REGISTER_INDEX=> self.finished_boot = value != 0, + JOYP_REGISTER_INDEX => { + let joypad_value = self.ports[JOYP_REGISTER_INDEX as usize]; + value = (joypad_value & 0xF) | (value & 0xF0); + } + // TODO: handle gbc registers (expecailly ram and vram) + _=>{} + } + + self.ports[address as usize] = value; + } +} + +impl UnprotectedMemory for IoComponents{ + fn read_unprotected(&self, address:u16)->u8 { + self.ports[address as usize] + } + + fn write_unprotected(&mut self, address:u16, value:u8) { + self.ports[address as usize] = value; + } +} + +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.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/mmu/io_ports.rs b/lib_gb/src/mmu/io_ports.rs index 10c799e1..58dc253f 100644 --- a/lib_gb/src/mmu/io_ports.rs +++ b/lib_gb/src/mmu/io_ports.rs @@ -1,11 +1,9 @@ use crate::utils::memory_registers::*; -use super::memory::{UnprotectedMemory, Memory}; pub const IO_PORTS_SIZE:usize = 0x80; pub const IO_PORTS_MEMORY_OFFSET:u16 = 0xFF00; -#[macro_use] macro_rules! io_port_index{ ($name:ident, $reg_address:expr) => { const $name:u16 = $reg_address - IO_PORTS_MEMORY_OFFSET; @@ -18,10 +16,11 @@ macro_rules! pub_io_port_index{ } pub_io_port_index!(DIV_REGISTER_INDEX, DIV_REGISTER_ADDRESS); +pub_io_port_index!(TAC_REGISTER_INDEX, TAC_REGISTER_ADDRESS); +pub_io_port_index!(TIMA_REGISTER_INDEX, TIMA_REGISTER_ADDRESS); +pub_io_port_index!(TMA_REGISTER_INDEX, TMA_REGISTER_ADDRESS); -io_port_index!(TAC_REGISTER_INDEX, TAC_REGISTER_ADDRESS); -io_port_index!(STAT_REGISTER_INDEX, STAT_REGISTER_ADDRESS); -io_port_index!(JOYP_REGISTER_INDEX, JOYP_REGISTER_ADDRESS); +pub_io_port_index!(JOYP_REGISTER_INDEX, JOYP_REGISTER_ADDRESS); pub_io_port_index!(NR10_REGISTER_INDEX, NR10_REGISTER_ADDRESS); pub_io_port_index!(NR11_REGISTER_INDEX, NR11_REGISTER_ADDRESS); pub_io_port_index!(NR12_REGISTER_INDEX, NR12_REGISTER_ADDRESS); @@ -40,92 +39,6 @@ pub_io_port_index!(NR41_REGISTER_INDEX, NR41_REGISTER_ADDRESS); pub_io_port_index!(NR42_REGISTER_INDEX, NR42_REGISTER_ADDRESS); pub_io_port_index!(NR43_REGISTER_INDEX, NR43_REGISTER_ADDRESS); pub_io_port_index!(NR44_REGISTER_INDEX, NR44_REGISTER_ADDRESS); -pub_io_port_index!(NR52_REGISTER_INDEX, NR52_REGISTER_ADDRESS); - -pub struct IoPorts{ - ports:[u8;IO_PORTS_SIZE], - ports_cycle_trigger:[bool; IO_PORTS_SIZE] -} - -impl Memory for IoPorts{ - fn read(&self, address:u16)->u8{ - let value = self.ports[address as usize]; - match address{ - NR10_REGISTER_INDEX=> value | 0b1000_0000, - NR11_REGISTER_INDEX=> value | 0b0011_1111, - NR13_REGISTER_INDEX=> 0xFF, - NR14_REGISTER_INDEX=> value | 0b1011_1111, - 0x15 => 0xFF, //Not used - NR21_REGISTER_INDEX=> value | 0b0011_1111, - NR23_REGISTER_INDEX=> 0xFF, - NR24_REGISTER_INDEX=> value | 0b1011_1111, - NR30_REGISTER_INDEX=> value | 0b0111_1111, - NR31_REGISTER_INDEX=> value | 0xFF, - NR32_REGISTER_INDEX=> value | 0b1001_1111, - NR33_REGISTER_INDEX=> value | 0xFF, - NR34_REGISTER_INDEX=> value | 0b1011_1111, - 0x1F => 0xFF, //Not used - NR41_REGISTER_INDEX=> 0xFF, - NR44_REGISTER_INDEX=> value | 0b1011_1111, - NR52_REGISTER_INDEX=> value | 0b0111_0000, - 0x27..=0x2F => 0xFF, //Not used - TAC_REGISTER_INDEX=> value & 0b111, - JOYP_REGISTER_INDEX => { - let joypad_value = self.ports[JOYP_REGISTER_INDEX as usize]; - (joypad_value & 0xF) | (value & 0xF0) - } - _=>value - } - } - - fn write(&mut self, address:u16, mut value:u8){ - match address{ - DIV_REGISTER_INDEX=> value = 0, - TAC_REGISTER_INDEX=> value &= 0b111, - STAT_REGISTER_INDEX => value = (value >> 2) << 2, - JOYP_REGISTER_INDEX => { - let joypad_value = self.ports[JOYP_REGISTER_INDEX as usize]; - value = (joypad_value & 0xF) | (value & 0xF0); - } - _=>{} - } - - self.ports_cycle_trigger[address as usize] = true; - - self.ports[address as usize] = value; - } -} - -impl UnprotectedMemory for IoPorts{ - fn write_unprotected(&mut self, address:u16, value:u8){ - self.ports[address as usize] = value; - } - - fn read_unprotected(&self, address:u16) ->u8 { - self.ports[address as usize] - } -} - -impl IoPorts{ - pub fn get_ports_cycle_trigger(&mut self)->&mut [bool; IO_PORTS_SIZE]{ - return &mut self.ports_cycle_trigger; - } - - pub fn clear_io_ports_triggers(&mut self){ - unsafe{std::ptr::write_bytes::(self.ports_cycle_trigger.as_mut_ptr(), 0, IO_PORTS_SIZE);} - } -} - -impl Default for IoPorts{ - fn default()->Self{ - let mut io_ports = IoPorts{ - ports:[0;IO_PORTS_SIZE], - ports_cycle_trigger:[false;IO_PORTS_SIZE] - }; - - //joypad register initiall value - io_ports.ports[JOYP_REGISTER_INDEX as usize] = 0xFF; - - io_ports - } -} \ No newline at end of file +pub_io_port_index!(NR50_REGISTER_INDEX, NR50_REGISTER_ADDRESS); +pub_io_port_index!(NR51_REGISTER_INDEX, NR51_REGISTER_ADDRESS); +pub_io_port_index!(NR52_REGISTER_INDEX, NR52_REGISTER_ADDRESS); \ No newline at end of file diff --git a/lib_gb/src/mmu/mmu_register_updater.rs b/lib_gb/src/mmu/mmu_register_updater.rs deleted file mode 100644 index 69cbe119..00000000 --- a/lib_gb/src/mmu/mmu_register_updater.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::{ppu::ppu_state::PpuState, utils::memory_registers::*}; -use super::{access_bus::AccessBus, gb_mmu::GbMmu, io_ports::IO_PORTS_MEMORY_OFFSET, memory::UnprotectedMemory, oam_dma_transferer::OamDmaTransferer}; - -const DMA_REGISTER_INDEX:usize = (DMA_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; - -pub fn update_mmu_registers(memory: &mut GbMmu,dma:&mut OamDmaTransferer){ - - handle_ppu_state(memory, memory.read_unprotected(STAT_REGISTER_ADDRESS)); - handle_wram_register(memory, memory.read_unprotected(SVBK_REGISTER_ADDRESS)); - handle_bootrom_register(memory, memory.read_unprotected(BOOT_REGISTER_ADDRESS)); - let ports = memory.io_ports.get_ports_cycle_trigger(); - if ports[DMA_REGISTER_INDEX]{ - ports[DMA_REGISTER_INDEX] = false; - handle_dma_transfer_register(memory.read_unprotected(DMA_REGISTER_ADDRESS), dma, memory); - } - else{ - memory.dma_state = dma.enable; - } -} - -fn handle_ppu_state(memory:&mut GbMmu, stat:u8){ - memory.ppu_state = PpuState::from_u8(stat & 0b0000_0011); -} - -fn handle_wram_register(memory: &mut GbMmu, register:u8){ - let bank:u8 = register & 0b00000111; - memory.ram.set_bank(bank); -} - -fn handle_bootrom_register(memory: &mut GbMmu, register:u8){ - memory.finished_boot = register == 1; -} - -fn handle_dma_transfer_register(register:u8, dma: &mut OamDmaTransferer, mmu:&mut GbMmu){ - dma.soure_address = (register as u16) << 8; - dma.enable = match register{ - 0..=0x7F=>Some(AccessBus::External), - 0x80..=0x9F=>Some(AccessBus::Video), - 0xA0..=0xFF=>Some(AccessBus::External) - }; - - mmu.dma_state = dma.enable; -} \ No newline at end of file diff --git a/lib_gb/src/mmu/mod.rs b/lib_gb/src/mmu/mod.rs index 890e7c99..886978cb 100644 --- a/lib_gb/src/mmu/mod.rs +++ b/lib_gb/src/mmu/mod.rs @@ -5,6 +5,6 @@ pub mod vram; #[macro_use] pub mod io_ports; pub mod carts; -pub mod access_bus; -pub mod mmu_register_updater; -pub mod oam_dma_transferer; \ No newline at end of file +pub mod access_bus; +pub mod oam_dma_transfer; +pub mod io_components; \ No newline at end of file diff --git a/lib_gb/src/mmu/oam_dma_transfer.rs b/lib_gb/src/mmu/oam_dma_transfer.rs new file mode 100644 index 00000000..703d74d1 --- /dev/null +++ b/lib_gb/src/mmu/oam_dma_transfer.rs @@ -0,0 +1,13 @@ +use super::access_bus::AccessBus; + +pub struct OamDmaTransfer{ + pub soure_address:u16, + pub enable:Option, + pub dma_cycle_counter:u16 +} + +impl Default for OamDmaTransfer{ + fn default() -> Self { + OamDmaTransfer{dma_cycle_counter:0, enable:None, soure_address:0} + } +} \ No newline at end of file diff --git a/lib_gb/src/mmu/oam_dma_transferer.rs b/lib_gb/src/mmu/oam_dma_transferer.rs deleted file mode 100644 index a24034e6..00000000 --- a/lib_gb/src/mmu/oam_dma_transferer.rs +++ /dev/null @@ -1,34 +0,0 @@ -use super::{access_bus::AccessBus, memory::UnprotectedMemory}; - -const DMA_SIZE:u16 = 0xA0; -const DMA_DEST:u16 = 0xFE00; - -pub struct OamDmaTransferer{ - pub soure_address:u16, - pub enable:Option, - dma_cycle_counter:u16 -} - -impl Default for OamDmaTransferer{ - fn default() -> Self { - OamDmaTransferer{dma_cycle_counter:0, enable:None, soure_address:0} - } -} - -impl OamDmaTransferer{ - pub fn cycle(&mut self,memory:&mut impl UnprotectedMemory, m_cycles:u8){ - if self.enable.is_some(){ - let cycles_to_run = std::cmp::min(self.dma_cycle_counter + m_cycles as u16, DMA_SIZE); - for i in self.dma_cycle_counter..cycles_to_run as u16{ - memory.write_unprotected(DMA_DEST + i, memory.read_unprotected(self.soure_address + i)); - } - - self.dma_cycle_counter += m_cycles as u16; - - if self.dma_cycle_counter >= DMA_SIZE{ - self.enable = None; - self.dma_cycle_counter = 0; - } - } - } -} \ No newline at end of file diff --git a/lib_gb/src/mmu/vram.rs b/lib_gb/src/mmu/vram.rs index 5395140a..d1ba629d 100644 --- a/lib_gb/src/mmu/vram.rs +++ b/lib_gb/src/mmu/vram.rs @@ -6,8 +6,7 @@ pub struct VRam{ } impl VRam{ - pub fn set_bank(&mut self, bank:u8) - { + pub fn set_bank(&mut self, bank:u8){ self.current_bank_register = bank; } 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 5fa54ee7..4a5b2dd6 100644 --- a/lib_gb/src/ppu/gb_ppu.rs +++ b/lib_gb/src/ppu/gb_ppu.rs @@ -1,59 +1,37 @@ -use crate::mmu::memory::UnprotectedMemory; -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::{ - memory_registers::*, - bit_masks::* -}; -use std::cmp; +use crate::utils::{vec2::Vec2, bit_masks::*}; +use crate::mmu::vram::VRam; +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; +pub const BUFFERS_NUMBER:usize = 2; -//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 OAM_ADDRESS:u16 = 0xFE00; -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 OAM_ENTRY_SIZE:u16 = 4; +const OAM_MEMORY_SIZE:usize = 0xA0; -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 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 struct GbPpu{ + pub vram: VRam, + 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], - pub current_line_drawn: u8, - pub state:PpuState, //interrupts pub v_blank_interrupt_request:bool, @@ -61,449 +39,270 @@ 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_buffers: [[u32; SCREEN_HEIGHT * SCREEN_WIDTH];BUFFERS_NUMBER], + current_screen_buffer_index:usize, + 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 { - 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_buffers:[[0;SCREEN_HEIGHT * SCREEN_WIDTH];BUFFERS_NUMBER], + current_screen_buffer_index:0, + 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::Hblank, //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.t_cycles_passed = 0; + //This is an expensive operation! + unsafe{std::ptr::write_bytes(self.screen_buffers[self.current_screen_buffer_index].as_mut_ptr(), 0xFF, SCREEN_HEIGHT * SCREEN_WIDTH)}; + self.swap_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, memory:&mut impl UnprotectedMemory){ - if self.current_line_drawn >= SCREEN_HEIGHT as u8 && !self.v_blank_triggered{ - let mut if_register = memory.read_unprotected(IF_REGISTER_ADDRESS); - if_register |= BIT_0_MASK; - memory.write_unprotected(IF_REGISTER_ADDRESS, if_register); - - 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 i in 0..self.push_lcd_buffer.len(){ + self.screen_buffers[self.current_screen_buffer_index][self.screen_buffer_index] = u32::from(self.push_lcd_buffer[i]); + self.screen_buffer_index += 1; + if self.screen_buffer_index == SCREEN_WIDTH * SCREEN_HEIGHT{ + self.swap_buffer(); + } } - memory.write_unprotected(LY_REGISTER_ADDRESS, self.current_line_drawn); + self.push_lcd_buffer.clear(); } - fn update_stat_register(&mut self, memory: &mut impl UnprotectedMemory){ - let mut register = memory.read_unprotected(STAT_REGISTER_ADDRESS); - let mut lcd_stat_interrupt:bool = false; + fn swap_buffer(&mut self){ + self.gfx_device.swap_buffer(&self.screen_buffers[self.current_screen_buffer_index]); + self.screen_buffer_index = 0; + self.current_screen_buffer_index = (self.current_screen_buffer_index + 1) % BUFFERS_NUMBER; + } - if self.current_line_drawn == memory.read_unprotected(LYC_REGISTER_ADDRESS){ - 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{ - register &= !BIT_2_MASK; - } - - //clears the 2 lower bits - register = (register >> 2)<<2; - 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; - } - }, - _=>{} + self.stat_register &= !BIT_2_MASK; } - - if lcd_stat_interrupt{ + if self.trigger_stat_interrupt{ if !self.stat_triggered{ - let mut if_register = memory.read_unprotected(IF_REGISTER_ADDRESS); - if_register |= BIT_1_MASK; - memory.write_unprotected(IF_REGISTER_ADDRESS, if_register); - + *if_register |= BIT_1_MASK; self.stat_triggered = true; } } else{ self.stat_triggered = false; } - - memory.write_unprotected(STAT_REGISTER_ADDRESS, register); + 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") - }; - } + 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}; - pub fn update_gb_screen(&mut self, memory: &mut impl UnprotectedMemory, 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(memory); - self.update_stat_register(memory); - - 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(memory); - self.draw_window_frame_buffer(memory, &mut frame_buffer_line); - self.draw_objects_frame_buffer(memory, &mut frame_buffer_line); - - 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 get_bg_frame_buffer(&self, memory: &impl UnprotectedMemory)-> [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 = memory.read_unprotected(address + (index*BG_SPRITES_PER_LINE) + i); - let sprite = Self::get_normal_sprite(chr, memory, 0x8000); - line_sprites.push(sprite); - } - } - else { - for i in 0..BG_SPRITES_PER_LINE { - let mut chr: u8 = memory.read_unprotected(address + (index*BG_SPRITES_PER_LINE) + i); - chr = chr.wrapping_add(0x80); - let sprite = Self::get_normal_sprite(chr, memory, 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(index:u8, memory:&impl UnprotectedMemory, 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(memory, &mut sprite, data_address + j, line_number); - line_number += 1; - } - - return sprite; - } - - - fn draw_window_frame_buffer(&mut self, memory: &impl UnprotectedMemory, 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 = memory.read_unprotected(address + (index*BG_SPRITES_PER_LINE) + i); - let sprite = Self::get_normal_sprite(chr, memory, 0x8000); - line_sprites.push(sprite); - } - } - else { - for i in 0..BG_SPRITES_PER_LINE { - let mut chr: u8 = memory.read_unprotected(address + (index*BG_SPRITES_PER_LINE) + i); - chr = chr.wrapping_add(0x80); - let sprite = Self::get_normal_sprite(chr, memory, 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, memory:&impl UnprotectedMemory, 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 = memory.read_unprotected(OAM_ADDRESS + i); - let end_x = memory.read_unprotected(OAM_ADDRESS + i + 1); - 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 = memory.read_unprotected(OAM_ADDRESS + i + 2); - let attributes = memory.read_unprotected(OAM_ADDRESS + i + 3); - - 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, memory, 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(mut index:u8, memory:&impl UnprotectedMemory, 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(memory, raw, data_address + j, line_number); - line_number += 1; - } - unsafe{sprite = Box::from_raw(raw);} - - return sprite; - } - - fn get_line(memory:&impl UnprotectedMemory, sprite:*mut dyn Sprite, address:u16, line_number:u8){ - let byte = memory.read_unprotected(address); - let next = memory.read_unprotected(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..017acacf --- /dev/null +++ b/lib_gb/src/ppu/gfx_device.rs @@ -0,0 +1,5 @@ +use super::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; + +pub trait GfxDevice{ + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]); +} \ 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 6bfda866..2157ac41 100644 --- a/lib_gb/src/ppu/ppu_register_updater.rs +++ b/lib_gb/src/ppu/ppu_register_updater.rs @@ -1,78 +1,44 @@ -use crate::mmu::{gb_mmu::GbMmu, io_ports::IO_PORTS_MEMORY_OFFSET, memory::UnprotectedMemory}; -use crate::utils::{memory_registers::*, bit_masks::*}; -use super::{ gb_ppu::GbPpu, color::*, colors::*}; +use crate::utils::bit_masks::*; +use super::{color::*, colors::*, gb_ppu::GbPpu, gfx_device::GfxDevice}; const WX_OFFSET:u8 = 7; -const LCDC_REGISTER_INDEX:usize = (LCDC_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; -const STAT_REGISTER_INDEX:usize = (STAT_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; -const SCX_REGISTER_INDEX:usize = (SCX_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; -const SCY_REGISTER_INDEX:usize = (SCY_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; -const BGP_REGISTER_INDEX:usize = (BGP_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; -const OBP0_REGISTER_INDEX:usize = (OBP0_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; -const OBP1_REGISTER_INDEX:usize = (OBP1_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; -const WX_REGISTER_INDEX:usize = (WX_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; -const WY_REGISTER_INDEX:usize = (WY_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; - -pub fn update_ppu_regsiters(memory:&mut GbMmu, ppu: &mut GbPpu){ - if memory.io_ports.get_ports_cycle_trigger()[LCDC_REGISTER_INDEX] { - handle_lcdcontrol_register(memory.read_unprotected(LCDC_REGISTER_ADDRESS), ppu); - } - if memory.io_ports.get_ports_cycle_trigger()[STAT_REGISTER_INDEX]{ - update_stat_register(memory.read_unprotected(STAT_REGISTER_ADDRESS), ppu); - } - if memory.io_ports.get_ports_cycle_trigger()[SCX_REGISTER_INDEX] || memory.io_ports.get_ports_cycle_trigger()[SCY_REGISTER_INDEX]{ - handle_scroll_registers(memory.read_unprotected(SCX_REGISTER_ADDRESS), memory.read_unprotected(SCY_REGISTER_ADDRESS), ppu); - } - if memory.io_ports.get_ports_cycle_trigger()[BGP_REGISTER_INDEX]{ - handle_bg_pallet_register(memory.read_unprotected(BGP_REGISTER_ADDRESS), &mut ppu.bg_color_mapping); - } - if memory.io_ports.get_ports_cycle_trigger()[OBP0_REGISTER_INDEX]{ - handle_obp_pallet_register(memory.read_unprotected(OBP0_REGISTER_ADDRESS), &mut ppu.obj_color_mapping0); +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(); } - if memory.io_ports.get_ports_cycle_trigger()[OBP1_REGISTER_INDEX]{ - handle_obp_pallet_register(memory.read_unprotected(OBP1_REGISTER_ADDRESS), &mut ppu.obj_color_mapping1); + else if ppu.lcd_control & BIT_7_MASK == 0 && register & BIT_7_MASK != 0{ + ppu.turn_on(); } - if memory.io_ports.get_ports_cycle_trigger()[WY_REGISTER_INDEX]{ - handle_wy_register(memory.read_unprotected(WY_REGISTER_ADDRESS), ppu); - } - if memory.io_ports.get_ports_cycle_trigger()[WX_REGISTER_INDEX]{ - handle_wx_register(memory.read_unprotected(WX_REGISTER_ADDRESS), ppu); - } -} - - -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; + + ppu.lcd_control = register; } -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; ppu.coincidence_interrupt_request = register & BIT_6_MASK != 0; + + ppu.stat_register = register & 0b111_1000; } -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; } -fn handle_bg_pallet_register(register:u8, pallet:&mut [Color;4] ){ +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] ){ pallet[0] = get_matching_color(register&0b00000011); pallet[1] = get_matching_color((register&0b00001100)>>2); pallet[2] = get_matching_color((register&0b00110000)>>4); pallet[3] = get_matching_color((register&0b11000000)>>6); } -fn handle_obp_pallet_register(register:u8, pallet:&mut [Option;4] ){ +pub fn handle_obp_pallet_register(register:u8, pallet:&mut [Option;4] ){ pallet[0] = None; pallet[1] = Some(get_matching_color((register&0b00001100)>>2)); pallet[2] = Some(get_matching_color((register&0b00110000)>>4)); @@ -89,15 +55,27 @@ fn get_matching_color(number:u8)->Color{ }; } -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; } -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.ly_register +} + +pub fn get_stat(ppu:&GbPpu)->u8{ + ppu.stat_register +} + +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/timer/gb_timer.rs b/lib_gb/src/timer/gb_timer.rs index 9761cc7e..444a2f1e 100644 --- a/lib_gb/src/timer/gb_timer.rs +++ b/lib_gb/src/timer/gb_timer.rs @@ -1,9 +1,13 @@ -use crate::{mmu::memory::UnprotectedMemory, utils::{bit_masks::*, memory_registers::*}}; +use crate::utils::bit_masks::*; pub struct GbTimer{ pub system_counter:u16, pub tima_overflow:bool, + pub tima_register:u8, + pub tma_register:u8, + pub tac_tegister:u8, + last_and_result:bool, reload_cooldown_counter:u8 } @@ -12,6 +16,9 @@ impl Default for GbTimer{ fn default() -> Self { GbTimer{ system_counter:0, + tima_register:0, + tma_register:0, + tac_tegister:0, last_and_result: false, reload_cooldown_counter: 0, tima_overflow:false @@ -20,9 +27,8 @@ impl Default for GbTimer{ } impl GbTimer{ - pub fn cycle(&mut self, memory:&mut impl UnprotectedMemory, m_cycles:u8){ - let mut tima_register = memory.read_unprotected(TIMA_REGISTER_ADDRESS); - let (timer_interval, timer_enable) = Self::get_timer_controller_data(memory); + pub fn cycle(&mut self, if_register:&mut u8, m_cycles:u8){ + let (timer_interval, timer_enable) = self.get_timer_controller_data(); for _ in 0..m_cycles * 4{ if timer_enable && self.tima_overflow{ @@ -30,10 +36,8 @@ impl GbTimer{ if self.reload_cooldown_counter >= 4{ self.reload_cooldown_counter = 0; - let mut if_register = memory.read_unprotected(IF_REGISTER_ADDRESS); - if_register |= BIT_2_MASK; - memory.write_unprotected(IF_REGISTER_ADDRESS, if_register); - tima_register = memory.read_unprotected(TMA_REGISTER_ADDRESS); + *if_register |= BIT_2_MASK; + self.tima_register = self.tma_register; self.tima_overflow = false; } } @@ -48,28 +52,20 @@ impl GbTimer{ _=> std::panic!("bad timer interval vlaue: {}", timer_interval) }; - if self.last_and_result && !timer_enable{ - println!("edge case"); - } let current_and_result = bit_value && timer_enable; if !current_and_result && self.last_and_result{ - let(value, overflow) = tima_register.overflowing_add(1); - tima_register = value; + let(value, overflow) = self.tima_register.overflowing_add(1); + self.tima_register = value; self.tima_overflow = overflow; self.reload_cooldown_counter = 0; } self.last_and_result = current_and_result; } - - memory.write_unprotected(DIV_REGISTER_ADDRESS, (self.system_counter >> 8) as u8); - memory.write_unprotected(TIMA_REGISTER_ADDRESS, tima_register); - } - fn get_timer_controller_data(memory: &mut impl UnprotectedMemory)->(u8, bool){ - let timer_controller = memory.read_unprotected(TAC_REGISTER_ADDRESS); - let timer_enable:bool = timer_controller & BIT_2_MASK != 0; + fn get_timer_controller_data(&self)->(u8, bool){ + let timer_enable:bool = self.tac_tegister & BIT_2_MASK != 0; - return (timer_controller & 0b11, timer_enable); + return (self.tac_tegister & 0b11, timer_enable); } } \ No newline at end of file diff --git a/lib_gb/src/timer/timer_register_updater.rs b/lib_gb/src/timer/timer_register_updater.rs index bf101a0a..b925f3e6 100644 --- a/lib_gb/src/timer/timer_register_updater.rs +++ b/lib_gb/src/timer/timer_register_updater.rs @@ -1,16 +1,23 @@ -use crate::{mmu::io_ports::{IoPorts, DIV_REGISTER_INDEX, IO_PORTS_MEMORY_OFFSET}, utils::memory_registers::TIMA_REGISTER_ADDRESS}; use super::gb_timer::GbTimer; -const TIMA_REGISTER_INDEX: usize = (TIMA_REGISTER_ADDRESS - IO_PORTS_MEMORY_OFFSET) as usize; +pub fn get_div(timer: &GbTimer)->u8{ + (timer.system_counter >> 8) as u8 +} -pub fn update_timer_registers(timer:&mut GbTimer, memory:&mut IoPorts){ - let ports = memory.get_ports_cycle_trigger(); - if ports[DIV_REGISTER_INDEX as usize]{ - timer.system_counter = 0; - ports[DIV_REGISTER_INDEX as usize] = false; - } - if ports[TIMA_REGISTER_INDEX]{ - timer.tima_overflow = false; - ports[TIMA_REGISTER_INDEX] = false; - } +pub fn set_tima(timer: &mut GbTimer, value:u8){ + timer.tima_register = value; + timer.tima_overflow = false; +} + +pub fn set_tma(timer: &mut GbTimer, value:u8){ + timer.tma_register = value; +} + +pub fn set_tac(timer: &mut GbTimer, value:u8){ + timer.tac_tegister = value & 0b111; +} + +//Reset on write +pub fn reset_div(timer: &mut GbTimer){ + timer.system_counter = 0; } \ 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/memory_registers.rs b/lib_gb/src/utils/memory_registers.rs index df69b742..3386a5ff 100644 --- a/lib_gb/src/utils/memory_registers.rs +++ b/lib_gb/src/utils/memory_registers.rs @@ -38,5 +38,4 @@ pub const OBP1_REGISTER_ADDRESS:u16 = 0xFF49; pub const WY_REGISTER_ADDRESS:u16 = 0xFF4A; pub const WX_REGISTER_ADDRESS:u16 = 0xFF4B; pub const BOOT_REGISTER_ADDRESS:u16 = 0xFF50; -pub const SVBK_REGISTER_ADDRESS:u16 = 0xFF70; pub const IE_REGISTER_ADDRESS:u16 = 0xFFFF; \ No newline at end of file diff --git a/lib_gb/src/utils/mod.rs b/lib_gb/src/utils/mod.rs index ef58f315..722f0216 100644 --- a/lib_gb/src/utils/mod.rs +++ b/lib_gb/src/utils/mod.rs @@ -1,5 +1,26 @@ +use std::mem::MaybeUninit; + pub mod vec2; pub mod memory_registers; pub mod bit_masks; +pub mod fixed_size_queue; + +// Frequency in m_cycles (m_cycle = 4 t_cycles) +pub const GB_FREQUENCY:u32 = 4_194_304 / 4; + +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 diff --git a/lib_gb/tests/integration_tests.rs b/lib_gb/tests/integration_tests.rs new file mode 100644 index 00000000..53520119 --- /dev/null +++ b/lib_gb/tests/integration_tests.rs @@ -0,0 +1,154 @@ +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::io::Read; +use lib_gb::apu::audio_device::BUFFER_SIZE; +use lib_gb::ppu::gb_ppu::{SCREEN_HEIGHT, SCREEN_WIDTH}; +use lib_gb::{ + apu::audio_device::AudioDevice, keypad::joypad_provider::JoypadProvider, + machine::{gameboy::GameBoy, mbc_initializer::initialize_mbc}, ppu::gfx_device::GfxDevice +}; + +struct CheckHashGfxDevice{ + hash:u64, + last_hash_p:*mut u64, + found_p:*mut bool, +} +impl GfxDevice for CheckHashGfxDevice{ + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { + let mut s = DefaultHasher::new(); + buffer.hash(&mut s); + let hash = s.finish(); + unsafe{ + if *self.last_hash_p == hash && hash == self.hash{ + println!("{}", hash); + *self.found_p = true; + } + *self.last_hash_p = hash; + } + } +} + +struct StubAudioDevice; +impl AudioDevice for StubAudioDevice{ + fn push_buffer(&mut self, _buffer:&[lib_gb::apu::audio_device::StereoSample; BUFFER_SIZE]) {} +} + +struct StubJoypadProvider; +impl JoypadProvider for StubJoypadProvider{ + fn provide(&mut self, _joypad:&mut lib_gb::keypad::joypad::Joypad) {} +} + +#[test] +fn test_cpu_instrs(){ + let file_url = "https://raw.githubusercontent.com/retrio/gb-test-roms/master/cpu_instrs/cpu_instrs.gb"; + run_integration_test_from_url(file_url, 800, 3798827046966939676); +} + +#[test] +fn test_cpu_instrs_timing(){ + let file_url = "https://raw.githubusercontent.com/retrio/gb-test-roms/master/instr_timing/instr_timing.gb"; + run_integration_test_from_url(file_url, 100, 469033992149587554); +} + +#[test] +fn test_turtle_window_y_trigger(){ + run_turtle_integration_test("window_y_trigger.gb", 15511617103807079362); +} + +#[test] +fn test_turtle_window_y_trigger_wx_offscreen(){ + run_turtle_integration_test("window_y_trigger_wx_offscreen.gb", 15592061677463553443); +} + +fn run_turtle_integration_test(program_name:&str, hash:u64){ + let zip_url = "https://github.com/Powerlated/TurtleTests/releases/download/v1.0/release.zip"; + + let file = reqwest::blocking::get(zip_url).unwrap() + .bytes().unwrap(); + + let cursor = std::io::Cursor::new(file.as_ref()); + + let mut programs = zip::ZipArchive::new(cursor).unwrap(); + let zip_file = programs.by_name(program_name).unwrap(); + let program = zip_file.bytes().map(|x|x.unwrap()).collect::>(); + + run_integration_test(program, 100, hash, format!("The program: {} has failed", program_name)); +} + +fn run_integration_test_from_url(program_url:&str, frames_to_execute:u32, expected_hash:u64){ + let file = reqwest::blocking::get(program_url).unwrap() + .bytes().unwrap(); + + let program = Vec::from(file.as_ref()); + let fail_message = format!("The program {} has failed", program_url); + run_integration_test(program, frames_to_execute, expected_hash, fail_message); +} + +fn run_integration_test(program:Vec, frames_to_execute:u32, expected_hash:u64, fail_message:String){ + let mut mbc = initialize_mbc(program, None); + let mut last_hash:u64 = 0; + let mut found = false; + let mut gameboy = GameBoy::new( + &mut mbc, + StubJoypadProvider{}, + StubAudioDevice{}, + CheckHashGfxDevice{hash:expected_hash,last_hash_p:&mut last_hash, found_p:&mut found} + ); + + for _ in 0..frames_to_execute { + gameboy.cycle_frame(); + if found{ + return; + } + } + assert!(false, "{}", fail_message); +} + + + +// This function is for clcualting the hash of a new test rom +/// # Examples +/// +///``` +///#[test] +///fn calc_custom_rom_hash(){ +/// calc_hash("path_to_rom"); +///} +///``` +#[allow(dead_code)] +fn calc_hash(rom_path:&str){ + static mut FRAMES_COUNTER:u32 = 0; + static mut LAST_HASH:u64 = 0; + struct GetHashGfxDevice; + impl GfxDevice for GetHashGfxDevice{ + fn swap_buffer(&mut self, buffer:&[u32; SCREEN_HEIGHT * SCREEN_WIDTH]) { + unsafe{ + if FRAMES_COUNTER < 700{ + FRAMES_COUNTER += 1; + return; + } + } + let mut s = DefaultHasher::new(); + buffer.hash(&mut s); + let hash = s.finish(); + unsafe{ + if LAST_HASH == hash{ + println!("{}", hash); + std::process::exit(0); + } + LAST_HASH = hash; + } + } + } + + let program = std::fs::read(rom_path) + .expect("Error could not find file"); + + let program = Vec::from(program); + + let mut mbc = initialize_mbc(program, None); + + let mut gameboy = GameBoy::new(&mut mbc, StubJoypadProvider{}, StubAudioDevice{}, GetHashGfxDevice{}); + + loop {gameboy.cycle_frame();} +} \ No newline at end of file diff --git a/lib_gb/tests/timer.rs b/lib_gb/tests/timer.rs index d9ceebff..8d67b282 100644 --- a/lib_gb/tests/timer.rs +++ b/lib_gb/tests/timer.rs @@ -3,15 +3,16 @@ use lib_gb::apu::timer::Timer; #[test] fn timer_test(){ - let mut timer = Timer::new(512); + let count = 512; + let mut timer = Timer::new(count); let mut counter = 0; - for _ in 0..511{ + for _ in 0..(count/4)-1{ counter+=1; assert!(timer.cycle() == false); } counter+=1; assert!(timer.cycle() == true); - assert!(counter == 512) + assert!(counter == 512/4) } \ No newline at end of file