From a4b6a4e8b2909374bb5285cddd6953a28dcb6969 Mon Sep 17 00:00:00 2001 From: Lokathor Date: Tue, 13 Nov 2018 12:47:52 -0700 Subject: [PATCH] Revert "Deploy rust-console/gba to github.com/rust-console/gba.git:master" This reverts commit 2f1c243742109d1e44f7f7a3e4e1accce7ee0ad1. --- .gitignore | 14 + .travis.yml | 30 ++ Cargo.toml | 17 + LICENSE-APACHE2.txt | 201 ++++++++ README.md | 17 + book/book.toml | 8 + book/src/SUMMARY.md | 15 + book/src/ch0/index.md | 134 +++++ book/src/ch1/hello1.md | 193 +++++++ book/src/ch1/hello2.md | 114 +++++ book/src/ch1/index.md | 10 + book/src/ch1/io_registers.md | 33 ++ book/src/ch1/the_display_control_register.md | 112 ++++ book/src/ch1/video_memory_intro.md | 113 +++++ book/src/ch2/index.md | 22 + book/src/ch2/light_cycle.md | 119 +++++ book/src/ch2/the_key_input_register.md | 211 ++++++++ book/src/ch2/the_vcount_register.md | 71 +++ book/src/introduction.md | 28 + build.bat | 14 + crt0.s | 34 ++ .nojekyll => docs/.nojekyll | 0 .../FontAwesome}/css/font-awesome.css | 0 .../FontAwesome}/fonts/FontAwesome.ttf | Bin .../fonts/fontawesome-webfont.eot | Bin .../fonts/fontawesome-webfont.svg | 0 .../fonts/fontawesome-webfont.ttf | Bin .../fonts/fontawesome-webfont.woff | Bin .../fonts/fontawesome-webfont.woff2 | Bin ayu-highlight.css => docs/ayu-highlight.css | 0 book.js => docs/book.js | 0 {ch0 => docs/ch0}/index.html | 0 {ch1 => docs/ch1}/hello1.html | 0 {ch1 => docs/ch1}/hello2.html | 0 {ch1 => docs/ch1}/index.html | 0 {ch1 => docs/ch1}/io_registers.html | 0 .../ch1}/the_display_control_register.html | 0 {ch1 => docs/ch1}/video_memory_intro.html | 0 {ch2 => docs/ch2}/index.html | 0 {ch2 => docs/ch2}/light_cycle.html | 0 {ch2 => docs/ch2}/the_key_input_register.html | 0 {ch2 => docs/ch2}/the_vcount_register.html | 0 clipboard.min.js => docs/clipboard.min.js | 0 {css => docs/css}/chrome.css | 0 {css => docs/css}/general.css | 0 {css => docs/css}/print.css | 0 {css => docs/css}/variables.css | 0 elasticlunr.min.js => docs/elasticlunr.min.js | 0 favicon.png => docs/favicon.png | Bin highlight.css => docs/highlight.css | 0 highlight.js => docs/highlight.js | 0 index.html => docs/index.html | 7 - introduction.html => docs/introduction.html | 7 - mark.min.js => docs/mark.min.js | 0 print.html => docs/print.html | 7 - searcher.js => docs/searcher.js | 0 docs/searchindex.js | 1 + docs/searchindex.json | 1 + tomorrow-night.css => docs/tomorrow-night.css | 0 examples/hello1.rs | 19 + examples/hello2.rs | 34 ++ examples/light_cycle.rs | 161 ++++++ linker.ld | 63 +++ rustfmt.toml | 8 + searchindex.js | 1 - searchindex.json | 1 - src/core_extras.rs | 50 ++ src/io_registers.rs | 477 ++++++++++++++++++ src/lib.rs | 27 + src/macros.rs | 8 + src/video_ram.rs | 49 ++ thumbv4-none-agb.json | 34 ++ 72 files changed, 2442 insertions(+), 23 deletions(-) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE2.txt create mode 100644 README.md create mode 100644 book/book.toml create mode 100644 book/src/SUMMARY.md create mode 100644 book/src/ch0/index.md create mode 100644 book/src/ch1/hello1.md create mode 100644 book/src/ch1/hello2.md create mode 100644 book/src/ch1/index.md create mode 100644 book/src/ch1/io_registers.md create mode 100644 book/src/ch1/the_display_control_register.md create mode 100644 book/src/ch1/video_memory_intro.md create mode 100644 book/src/ch2/index.md create mode 100644 book/src/ch2/light_cycle.md create mode 100644 book/src/ch2/the_key_input_register.md create mode 100644 book/src/ch2/the_vcount_register.md create mode 100644 book/src/introduction.md create mode 100644 build.bat create mode 100644 crt0.s rename .nojekyll => docs/.nojekyll (100%) rename {FontAwesome => docs/FontAwesome}/css/font-awesome.css (100%) rename {FontAwesome => docs/FontAwesome}/fonts/FontAwesome.ttf (100%) rename {FontAwesome => docs/FontAwesome}/fonts/fontawesome-webfont.eot (100%) rename {FontAwesome => docs/FontAwesome}/fonts/fontawesome-webfont.svg (100%) rename {FontAwesome => docs/FontAwesome}/fonts/fontawesome-webfont.ttf (100%) rename {FontAwesome => docs/FontAwesome}/fonts/fontawesome-webfont.woff (100%) rename {FontAwesome => docs/FontAwesome}/fonts/fontawesome-webfont.woff2 (100%) rename ayu-highlight.css => docs/ayu-highlight.css (100%) rename book.js => docs/book.js (100%) rename {ch0 => docs/ch0}/index.html (100%) rename {ch1 => docs/ch1}/hello1.html (100%) rename {ch1 => docs/ch1}/hello2.html (100%) rename {ch1 => docs/ch1}/index.html (100%) rename {ch1 => docs/ch1}/io_registers.html (100%) rename {ch1 => docs/ch1}/the_display_control_register.html (100%) rename {ch1 => docs/ch1}/video_memory_intro.html (100%) rename {ch2 => docs/ch2}/index.html (100%) rename {ch2 => docs/ch2}/light_cycle.html (100%) rename {ch2 => docs/ch2}/the_key_input_register.html (100%) rename {ch2 => docs/ch2}/the_vcount_register.html (100%) rename clipboard.min.js => docs/clipboard.min.js (100%) rename {css => docs/css}/chrome.css (100%) rename {css => docs/css}/general.css (100%) rename {css => docs/css}/print.css (100%) rename {css => docs/css}/variables.css (100%) rename elasticlunr.min.js => docs/elasticlunr.min.js (100%) rename favicon.png => docs/favicon.png (100%) rename highlight.css => docs/highlight.css (100%) rename highlight.js => docs/highlight.js (100%) rename index.html => docs/index.html (95%) rename introduction.html => docs/introduction.html (95%) rename mark.min.js => docs/mark.min.js (100%) rename print.html => docs/print.html (99%) rename searcher.js => docs/searcher.js (100%) create mode 100644 docs/searchindex.js create mode 100644 docs/searchindex.json rename tomorrow-night.css => docs/tomorrow-night.css (100%) create mode 100644 examples/hello1.rs create mode 100644 examples/hello2.rs create mode 100644 examples/light_cycle.rs create mode 100644 linker.ld create mode 100644 rustfmt.toml delete mode 100644 searchindex.js delete mode 100644 searchindex.json create mode 100644 src/core_extras.rs create mode 100644 src/io_registers.rs create mode 100644 src/lib.rs create mode 100644 src/macros.rs create mode 100644 src/video_ram.rs create mode 100644 thumbv4-none-agb.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba7bc1f --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# The crt0.o file should, under the currently suggested build scheme, be +# recompiled every build anyway. +crt0.o diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..27842e5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +language: rust +sudo: false + +cache: + - cargo + +rust: + - nightly + +before_script: + - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) + - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.1" mdbook) + - cargo install-update -a + +script: + - cargo check && cargo check --release + # at the moment we DO NOT run "cargo test" because of lang-item issues + - cd book && mdbook build + +deploy: + provider: pages + skip-cleanup: true + github-token: $GITHUB_TOKEN + local-dir: docs + target-branch: master + keep-history: true + name: DocsBot + verbose: true + on: + branch: master diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..543ddc8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "gba" +description = "A crate (and book) for making GBA games with Rust." +version = "0.1.0" +authors = ["Lokathor ", "Ketsuban"] +repository = "https://github.com/rust-console/gba" +readme = "README.md" +keywords = ["gba"] +edition = "2018" +license = "Apache-2.0" + +[dependencies] +gba-proc-macro = "0.1.1" + +[profile.release] +lto = true +panic = "abort" diff --git a/LICENSE-APACHE2.txt b/LICENSE-APACHE2.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE-APACHE2.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0afd17 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +[![License:Apache2](https://img.shields.io/badge/License-Apache2-green.svg)](https://www.apache.org/licenses/LICENSE-2.0) + +# gba + +A crate that helps you make GBA games + +# First Time Setup + +This crate requires a fair amount of special setup. All of the steps are +detailed for you [in the 0th chapter of the +book](https://rust-console.github.io/gba/ch0/index.html) that goes with this +crate. + +# Contribution + +This crate is Apache2 licensed and any contributions you submit must also be +Apache2 licensed. diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000..e684d12 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,8 @@ +[book] +title = "Rust GBA Tutorials" +authors = ["Lokathor"] +#description = "Rust GBA Tutorials." + +[build] +build-dir = "../docs" +create-missing = true diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000..6b3fee5 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,15 @@ + +# Rust GBA Tutorials + +* [Introduction](introduction.md) +* [Ch 0: Development Setup](ch0/index.md) +* [Ch 1: Hello GBA](ch1/index.md) + * [hello1](ch1/hello1.md) + * [IO Registers](ch1/io_registers.md) + * [The Display Control Register](ch1/the_display_control_register.md) + * [Video Memory Intro](ch1/video_memory_intro.md) + * [hello2](ch1/hello2.md) +* [Ch 2: User Input](ch2/index.md) + * [The Key Input Register](ch2/the_key_input_register.md) + * [The VCount Register](ch2/the_vcount_register.md) + * [light_cycle](ch2/light_cycle.md) diff --git a/book/src/ch0/index.md b/book/src/ch0/index.md new file mode 100644 index 0000000..803eb2c --- /dev/null +++ b/book/src/ch0/index.md @@ -0,0 +1,134 @@ +# Chapter 0: Development Setup + +Before you can build a GBA game you'll have to follow some special steps to +setup the development environment. Perhaps unfortunately, there's enough detail +here to warrant a mini-chapter all on its own. + +Before we begin I'd like to give a special thanks to **Ketsuban**, who is the +wizard that arranged for all of this to be able to happen and laid out the +details of the plan to the rest of the world. + +## Per System Setup + +Obviously you need your computer to have a working rust installation. However, +you'll also need to ensure that you're using a nightly toolchain. You can run +`rustup default nightly` to set nightly as the system wide default toolchain, or +you can use a [toolchain +file](https://github.com/rust-lang-nursery/rustup.rs#the-toolchain-file) to use +nightly just on a specific project, but either way we'll be assuming nightly +from now on. + +Next you need [devkitpro](https://devkitpro.org/wiki/Getting_Started). They've +got a graphical installer for Windows, and `pacman` support on Linux. We'll be +using a few of their binutils for the `arm-none-eabi` target, and we'll also be +using some of their tools that are specific to GBA development, so _even if_ you +already have the right binutils for whatever reason, you'll still want devkitpro +for the `gbafix` utility. + +* On Windows you'll want something like `C:\devkitpro\devkitARM\bin` and + `C:\devkitpro\tools\bin` to be [added to your + PATH](https://stackoverflow.com/q/44272416/455232), depending on where you + installed it to and such. +* On Linux you'll also want it to be added to your path, but if you're using + Linux I'll just assume you know how to do all that. + +Finally, you'll need `cargo-xbuild`. Just run `cargo install cargo-xbuild` and +cargo will figure it all out for you. + +## Per Project Setup + +Now you'll need some particular files each time you want to start a new project. +You can find them in the root of the [rust-console/gba +repo](https://github.com/rust-console/gba). + +* `thumbv4-none-agb.json` describes the overall GBA to cargo-xbuild so it knows + what to do. This is actually a somewhat made up target name since there's no + official target name. The GBA is essentially the same as a normal + `thumbv4-none-eabi` device, but we give it the "agb" signifier so that later + on we'll be able to use rust's `cfg` ability to allow our code to know if it's + specifically targeting a GBA or some other similar device (like an NDS). +* `crt0.s` describes some ASM startup stuff. If you have more ASM to place here + later on this is where you can put it. You also need to build it into a + `crt0.o` file before it can actually be used, but we'll cover that below. +* `linker.ld` tells the linker more critical info about the layout expectations + that the GBA has about our program. + +## Compiling + +The next steps only work once you've got some source code to build. If you need +a quick test, copy the `hello1.rs` file from our examples directory in the +repository. + +Once you've got something to build, you perform the following steps: + +* `arm-none-eabi-as crt0.s -o crt0.o` + * This builds your text format `crt0.s` file into object format `crt0.o`. You + don't need to perform it every time, only when `crt0.s` changes, but you + might as well do it every time so that you never forget to because it's a + practically instant operation. + +* `cargo xbuild --target thumbv4-none-agb.json` + * This builds your Rust source. It accepts _most of_ the normal options, such + as `--release`, and options, such as `--bin foo` or `--examples`, that you'd + expect `cargo` to accept. + * You **can not** build and run tests this way, because they require `std`, + which the GBA doesn't have. You can still run some of your project's tests + with `cargo test`, but that builds for your local machine, so anything + specific to the GBA (such as reading and writing registers) won't be + testable that way. If you want to isolate and try out some piece code + running on the GBA you'll unfortunately have to make a demo for it in your + `examples/` directory and then run the demo in an emulator and see if it + does what you expect. + * The file extension is important. `cargo xbuild` takes it as a flag to + compile dependencies with the same sysroot, so you can include crates + normally. Well, creates that work in the GBA's limited environment, but you + get the idea. + +At this point you have an ELF binary that some emulators can execute directly. +This is helpful because it'll have debug symbols and all that, assuming a debug +build. Specifically, [mgba 0.7 beta +1](https://mgba.io/2018/09/24/mgba-0.7-beta1/) can do it, and perhaps other +emulators can also do it. + +However, if you want a "real" ROM that works in all emulators and that you could +transfer to a flash cart there's a little more to do. + +* `arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/MODE/BIN_NAME target/ROM_NAME.gba` + * This will perform an [objcopy](https://linux.die.net/man/1/objcopy) on our + program. Here I've named the program `arm-none-eabi-objcopy`, which is what + devkitpro calls their version of `objcopy` that's specific to the GBA in the + Windows install. If the program isn't found under that name, have a look in + your installation directory to see if it's under a slightly different name + or something. + * As you can see from reading the man page, the `-O binary` option takes our + lovely ELF file with symbols and all that and strips it down to basically a + bare memory dump of the program. + * The next argument is the input file. You might not be familiar with how + `cargo` arranges stuff in the `target/` directory, and between RLS and + `cargo doc` and stuff it gets kinda crowded, so it goes like this: + * Since our program was built for a non-local target, first we've got a + directory named for that target, `thumbv4-none-agb/` + * Next, the "MODE" is either `debug/` or `release/`, depending on if we had + the `--release` flag included. You'll probably only be packing release + mode programs all the way into GBA roms, but it works with either mode. + * Finally, the name of the program. If your program is something out of the + project's `src/bin/` then it'll be that file's name, or whatever name you + configured for the bin in the `Cargo.toml` file. If your program is + something out of the project's `examples/` directory there will be a + similar `examples/` sub-directory first, and then the example's name. + * The final argument is the output of the `objcopy`, which I suggest putting + at just the top level of the `target/` directory. Really it could go + anywhere, but if you're using git then it's likely that your `.gitignore` + file is already setup to exclude everything in `target/`, so this makes sure + that your intermediate game builds don't get checked into your git. + +* `gbafix target/ROM_NAME.gba` + * The `gbafix` tool also comes from devkitpro. The GBA is very picky about a + ROM's format, and `gbafix` patches the ROM's header and such so that it'll + work right. Unlike `objcopy`, this tool is custom built for GBA development, + so it works just perfectly without any arguments beyond the file name. The + ROM is patched in place, so we don't even need to specify a new destination. + +And you're finally done! + +Of course, you probably want to make a script for all that, but it's up to you. diff --git a/book/src/ch1/hello1.md b/book/src/ch1/hello1.md new file mode 100644 index 0000000..2044d46 --- /dev/null +++ b/book/src/ch1/hello1.md @@ -0,0 +1,193 @@ +# hello1 + +Ready? Here goes: + +`hello1.rs` + +```rust +#![feature(start)] +#![no_std] + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + (0x04000000 as *mut u16).write_volatile(0x0403); + (0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F); + (0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0); + (0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00); + loop {} + } +} +``` + +Throw that into your project, build the program (as described back in Chapter +0), and give it a run. You should see a red, green, and blue dot close-ish to +the middle of the screen. If you don't, something already went wrong. Double +check things, phone a friend, write your senators, try asking Ketsuban on the +[Rust Community Discord](https://discordapp.com/invite/aVESxV8), until you're +able to get your three dots going. + +## Explaining hello1 + +So, what just happened? Even if you're used to Rust that might look pretty +strange. We'll go over each part extra carefully. + +```rust +#![feature(start)] +``` + +This enables the [start +feature](https://doc.rust-lang.org/beta/unstable-book/language-features/start.html), +which you would normally be able to read about in the unstable book, except that +the book tells you nothing at all except to look at the [tracking +issue](https://github.com/rust-lang/rust/issues/29633). + +Basically, a GBA game is even more low-level than the _normal_ amount of +low-level that you get from Rust, so we have to tell the compiler to account for +that by specifying a `#[start]`, and we need this feature on to do that. + +```rust +#![no_std] +``` + +There's no standard library available on the GBA, so we'll have to live a core +only life. + +```rust +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} +``` + +This sets our [panic +handler](https://doc.rust-lang.org/nightly/nomicon/panic-handler.html). +Basically, if we somehow trigger a panic, this is where the program goes. +However, right now we don't know how to get any sort of message out to the user +so... we do nothing at all. We _can't even return_ from here, so we just sit in +an infinite loop. The player will have to reset the universe from the outside. + +The `#[cfg(not(test))]` part makes this item only exist in the program when +we're _not_ in a test build. This is so that `cargo test` and such work right as +much as possible. + +```rust +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { +``` + +This is our `#[start]`. We call it `main`, but it's not like a `main` that you'd +see in a Rust program. It's _more like_ the sort of `main` that you'd see in a C +program, but it's still **not** that either. If you compile a `#[start]` program +for a target with an OS such as `arm-none-eabi-nm` you can open up the debug +info and see that your result will have the symbol for the C `main` along side +the symbol for the start `main` that we write here. Our start `main` is just its +own unique thing, and the inputs and outputs have to be like that because that's +how `#[start]` is specified to work in Rust. + +If you think about it for a moment you'll probably realize that, those inputs +and outputs are totally useless to us on a GBA. There's no OS on the GBA to call +our program, and there's no place for our program to "return to" when it's done. + +Side note: if you want to learn more about stuff "before main gets called" you +can watch a great [CppCon talk](https://www.youtube.com/watch?v=dOfucXtyEsU) by +Matt Godbolt (yes, that Godbolt) where he delves into quite a bit of it. The +talk doesn't really apply to the GBA, but it's pretty good. + +```rust + unsafe { +``` + +I hope you're all set for some `unsafe`, because there's a lot of it to be had. + +```rust + (0x04000000 as *mut u16).write_volatile(0x0403); +``` + +Sure! + +```rust + (0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F); + (0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0); + (0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00); +``` + +Ah, of course. + +```rust + loop {} + } +} +``` + +And, as mentioned above, there's no place for a GBA program to "return to", so +we can't ever let `main` try to return there. Instead, we go into an infinite +`loop` that does nothing. The fact that this doesn't ever return an `isize` +value doesn't seem to bother Rust, because I guess we're at least not returning +any other type of thing instead. + +Fun fact: unlike in C++, an infinite loop with no side effects isn't Undefined +Behavior for us rustaceans... _semantically_. In truth LLVM has a [known +bug](https://github.com/rust-lang/rust/issues/28728) in this area, so we won't +actually be relying on empty loops in any future programs. + +## All Those Magic Numbers + +Alright, I cheated quite a bit in the middle there. The program works, but I +didn't really tell you why because I didn't really tell you what any of those +magic numbers mean or do. + +* `0x04000000` is the address of an IO Register called the Display Control. +* `0x06000000` is the start of Video RAM. + +So we write some magic to the display control register once, then we write some +other magic to three locations of magic to the Video RAM. We get three dots, +each in their own location... so that second part makes sense at least. + +We'll get into the magic number details in the other sections of this chapter. + +## Sidebar: Volatile + +We'll get into what all that is in a moment, but first let's ask ourselves: Why +are we doing _volatile_ writes? You've probably never used it before at all. +What is volatile anyway? + +Well, the optimizer is pretty aggressive some of the time, and so it'll skip +reads and writes when it thinks can. Like if you write to a pointer once, and +then again a moment later, and it didn't see any other reads in between, it'll +think that it can just skip doing that first write since it'll get overwritten +anyway. Sometimes that's right, but sometimes it's wrong. + +Marking a read or write as _volatile_ tells the compiler that it really must do +that action, and in the exact order that we wrote it out. It says that there +might even be special hardware side effects going on that the compiler isn't +aware of. In this case, the write to the display control register sets a video +mode, and the writes to the Video RAM set pixels that will show up on the +screen. + +Similar to "atomic" operations you might have heard about, all volatile +operations are enforced to happen in the exact order that you specify them, but +only relative to other volatile operations. So something like + +```rust +c.volatile_write(5); +a += b; +d.volatile_write(7); +``` + +might end up changing `a` either before or after the change to `c` (since the +value of `a` doesn't affect the write to `c`), but the write to `d` will +_always_ happen after the write to `c` even though the compiler doesn't see any +direct data dependency there. + +If you ever use volatile stuff on other platforms it's important to note that +volatile doesn't make things thread-safe, you still need atomic for that. +However, the GBA doesn't have threads, so we don't have to worry about thread +safety concerns. diff --git a/book/src/ch1/hello2.md b/book/src/ch1/hello2.md new file mode 100644 index 0000000..1273a69 --- /dev/null +++ b/book/src/ch1/hello2.md @@ -0,0 +1,114 @@ +# hello2 + +Okay so let's have a look again: + +`hello1` + +```rust +#![feature(start)] +#![no_std] + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + (0x04000000 as *mut u16).write_volatile(0x0403); + (0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F); + (0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0); + (0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00); + loop {} + } +} +``` + +Now let's clean this up so that it's clearer what's going on. + +First we'll label that display control stuff: + +```rust +pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16; +pub const MODE3: u16 = 3; +pub const BG2: u16 = 0b100_0000_0000; +``` + +Next we make some const values for the actual pixel drawing + +```rust +pub const VRAM: usize = 0x06000000; +pub const SCREEN_WIDTH: isize = 240; +``` + +And then we want a small helper function for putting together a color value. + +Happily, this one can even be declared as a const function. At the time of +writing, we've got the "minimal const fn" support in nightly. It really is quite +limited, but I'm happy to let rustc and LLVM pre-compute as much as they can +when it comes to the GBA's tiny CPU. + +```rust +pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 { + blue << 10 | green << 5 | red +} +``` + +Finally, we'll make a function for drawing a pixel in Mode 3. Even though it's +just a one-liner, having the "important parts" be labeled as function arguments +usually helps you think about it a lot better. + +```rust +pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) { + (VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color); +} +``` + +So now we've got this: + +`hello2` + +```rust +#![feature(start)] +#![no_std] + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + DISPCNT.write_volatile(MODE3 | BG2); + mode3_pixel(120, 80, rgb16(31, 0, 0)); + mode3_pixel(136, 80, rgb16(0, 31, 0)); + mode3_pixel(120, 96, rgb16(0, 0, 31)); + loop {} + } +} + +pub const DISPCNT: *mut u16 = 0x04000000 as *mut u16; +pub const MODE3: u16 = 3; +pub const BG2: u16 = 0b100_0000_0000; + +pub const VRAM: usize = 0x06000000; +pub const SCREEN_WIDTH: isize = 240; + +pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 { + blue << 10 | green << 5 | red +} + +pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) { + (VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color); +} +``` + +Exact same program that we started with, but much easier to read. + +Of course, in the full `gba` crate that this book is a part of we have these and +other elements all labeled and sorted out for you. Still, for educational +purposes it's often best to do it yourself at least once. diff --git a/book/src/ch1/index.md b/book/src/ch1/index.md new file mode 100644 index 0000000..7c21c79 --- /dev/null +++ b/book/src/ch1/index.md @@ -0,0 +1,10 @@ +# Ch 1: Hello GBA + +Traditionally a person writes a "hello, world" program so that they can test +that their development environment is setup properly and to just get a feel for +using the tools involved. To get an idea of what a small part of a source file +will look like. All that stuff. + +Normally, you write a program that prints "hello, world" to the terminal. The +GBA has no terminal, but it does have a screen, so instead we're going to draw +three dots to the screen. diff --git a/book/src/ch1/io_registers.md b/book/src/ch1/io_registers.md new file mode 100644 index 0000000..890e7a7 --- /dev/null +++ b/book/src/ch1/io_registers.md @@ -0,0 +1,33 @@ +# IO Registers + +The GBA has a large number of **IO Registers** (not to be confused with CPU +registers). These are special memory locations from `0x04000000` to +`0x040003FE`. GBATEK has a [full +list](http://problemkaputt.de/gbatek.htm#gbaiomap), but we only need to learn +about a few of them at a time as we go, so don't be worried. + +The important facts to know about IO Registers are these: + +* Each has their own specific size. Most are `u16`, but some are `u32`. +* All of them must be accessed in a `volatile` style. +* Each register is specifically readable or writable or both. Actually, with + some registers there are even individual bits that are read-only or + write-only. + * If you write to a read-only position, those writes are simply ignored. This + mostly matters if a writable register contains a read-only bit (such as the + Display Control, next section). + * If you read from a write-only position, you get back values that are + [basically + nonsense](http://problemkaputt.de/gbatek.htm#gbaunpredictablethings). There + aren't really any registers that mix writable bits with read only bits, so + you're basically safe here. The only (mild) concern is that when you write a + value into a write-only register you need to keep track of what you wrote + somewhere else if you want to know what you wrote (such to adjust an offset + value by +1, or whatever). + * You can always check GBATEK to be sure, but if I don't mention it then a bit + is probably both read and write. +* Some registers have invalid bit patterns. For example, the lowest three bits + of the Display Control register can't legally be set to the values 6 or 7. + +When talking about bit positions, the numbers are _zero indexed_ just like an +array index is. diff --git a/book/src/ch1/the_display_control_register.md b/book/src/ch1/the_display_control_register.md new file mode 100644 index 0000000..6c20fdf --- /dev/null +++ b/book/src/ch1/the_display_control_register.md @@ -0,0 +1,112 @@ +# The Display Control Register + +The display control register is our first actual IO Register. GBATEK gives it the +shorthand [DISPCNT](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol), so +you might see it under that name if you read other guides. + +Among IO Registers, it's one of the simpler ones, but it's got enough complexity +that we can get a hint of what's to come. + +Also it's the one that you basically always need to set at least once in every +GBA game, so it's a good starting one to go over for that reason too. + +The display control register holds a `u16` value, and is located at `0x0400_0000`. + +Many of the bits here won't mean much to you right now. **That is fine.** You do +NOT need to memorize them all or what they all do right away. We'll just skim +over all the parts of this register to start, and then we'll go into more detail +in later chapters when we need to come back and use more of the bits. + +## Video Modes + +The lowest three bits (0-2) let you select from among the GBA's six video modes. +You'll notice that 3 bits allows for eight modes, but the values 6 and 7 are +prohibited. + +Modes 0, 1, and 2 are "tiled" modes. These are actually the modes that you +should eventually learn to use as much as possible. It lets the GBA's limited +video hardware do as much of the work as possible, leaving more of your CPU time +for gameplay computations. However, they're also complex enough to deserve their +own demos and chapters later on, so that's all we'll say about them for now. + +Modes 3, 4, and 5 are "bitmap" modes. These let you write individual pixels to +locations on the screen. + +* **Mode 3** is full resolution (240w x 160h) RGB15 color. You might not be used + to RGB15, since modern computers have 24 or 32 bit colors. In RGB15, there's 5 + bits for each color channel stored within a `u16` value, and the highest bit is + simply ignored. +* **Mode 4** is full resolution paletted color. Instead of being a `u16` color, each + pixel value is a `u8` palette index entry, and then the display uses the + palette memory (which we'll talk about later) to store the actual color data. + Since each pixel is half sized, we can fit twice as many. This lets us have + two "pages". At any given moment only one page is active, and you can draw to + the other page without the user noticing. You set which page to show with + another bit we'll get to in a moment. +* **Mode 5** is full color, but also with pages. This means that we must have a + reduced resolution to compensate (video memory is only so big!). The screen is + effectively only 160w x 128h in this mode. + +## CGB Mode + +Bit 3 is effectively read only. Technically it can be flipped using a BIOS call, +but when you write to the display control register normally it won't write to +this bit, so we'll call it effectively read only. + +This bit is on if the CPU is in CGB mode. + +## Page Flipping + +Bit 4 lets you pick which page to use. This is only relevent in video modes 4 or +5, and is just ignored otherwise. It's very easy to remember: when the bit is 0 +the 0th page is used, and when the bit is 1 the 1st page is used. + +The second page always starts at `0x0600_A000`. + +## OAM, VRAM, and Blanking + +Bit 5 lets you access OAM during HBlank if enabled. This is cool, but it reduces +the maximum sprites per scanline, so it's not default. + +Bit 6 lets you adjust if the GBA should treat Object Character VRAM as being 2d +(off) or 1d (on). This particular control can be kinda tricky to wrap your head +around, so we'll be sure to have some extra diagrams in the chapter that deals +with it. + +Bit 7 forces the screen to stay in VBlank as long as it's set. This allows the +fastest use of the VRAM, Palette, and Object Attribute Memory. Obviously if you +leave this on for too long the player will notice a blank screen, but it might +be okay to use for a moment or two every once in a while. + +## Screen Layers + +Bits 8 through 11 control if Background layers 0 through 3 should be active. + +Bit 12 affects the Object layer. + +Note that not all background layers are available in all video modes: + +* Mode 0: all +* Mode 1: 0/1/2 +* Mode 2: 2/3 +* Mode 3/4/5: 2 + +Bit 13 and 14 enable the display of Windows 0 and 1, and Bit 15 enables the +object display window. We'll get into how windows work later on, they let you do +some nifty graphical effects. + +## In Conclusion... + +So what did we do to the display control register in `hello1`? + +```rust + (0x04000000 as *mut u16).write_volatile(0x0403); +``` + +First let's [convert that to +binary](https://www.wolframalpha.com/input/?i=0x0403), and we get +`0b100_0000_0011`. So, that's setting Mode 3 with background 2 enabled and +nothing else special. + +However, I think we can do better than that. This is a prime target for more +newtyping as we attempt a `hello2` program. diff --git a/book/src/ch1/video_memory_intro.md b/book/src/ch1/video_memory_intro.md new file mode 100644 index 0000000..6b82d93 --- /dev/null +++ b/book/src/ch1/video_memory_intro.md @@ -0,0 +1,113 @@ +# Video Memory Intro + +The GBA's Video RAM is 96k stretching from `0x0600_0000` to `0x0601_7FFF`. + +The Video RAM can only be accessed totally freely during a Vertical Blank (aka +"VBlank", though sometimes I forget and don't capitalize it properly). At other +times, if the CPU tries to touch the same part of video memory as the display +controller is accessing then the CPU gets bumped by a cycle to avoid a clash. + +Annoyingly, VRAM can only be properly written to in 16 and 32 bit segments (same +with PALRAM and OAM). If you try to write just an 8 bit segment, then both parts +of the 16 bit segment get the same value written to them. In other words, if you +write the byte `5` to `0x0600_0000`, then both `0x0600_0000` and ALSO +`0x0600_0001` will have the byte `5` in them. We have to be extra careful when +trying to set an individual byte, and we also have to be careful if we use +`memcopy` or `memset` as well, because they're byte oriented by default and +don't know to follow the special rules. + +## RGB15 + +As I said before, RGB15 stores a color within a `u16` value using 5 bits for +each color channel. + +```rust +pub const RED: u16 = 0b0_00000_00000_11111; +pub const GREEN: u16 = 0b0_00000_11111_00000; +pub const BLUE: u16 = 0b0_11111_00000_00000; +``` + +In Mode 3 and Mode 5 we write direct color values into VRAM, and in Mode 4 we +write palette index values, and then the color values go into the PALRAM. + +## Mode 3 + +Mode 3 is pretty easy. We have a full resolution grid of rgb15 pixels. There's +160 rows of 240 pixels each, with the base address being the top left corner. A +particular pixel uses normal "2d indexing" math: + +```rust +let row_five_col_seven = 5 + (7 * SCREEN_WIDTH); +``` + +To draw a pixel, we just write a value at the address for the row and col that +we want to draw to. + +## Mode 4 + +Mode 4 introduces page flipping. Instead of one giant page at `0x0600_0000`, +there's Page 0 at `0x0600_0000` and then Page 1 at `0x0600_A000`. The resolution +for each page is the same as above, but instead of writing `u16` values, the +memory is treated as `u8` indexes into PALRAM. The PALRAM starts at +`0x0500_0000`, and there's enough space for 256 palette entries (each a `u16`). + +To set the color of a palette entry we just do a normal `u16` write_volatile. + +```rust +(0x0500_0000 as *mut u16).offset(target_index).write_volatile(new_color) +``` + +To draw a pixel we set the palette entry that we want the pixel to use. However, +we must remember the "minimum size" write limitation that applies to VRAM. So, +if we want to change just a single pixel at a time we must + +1) Read the full `u16` it's a part of. +2) Clear the half of the `u16` we're going to replace +3) Write the half of the `u16` we're going to replace with the new value +4) Write that result back to the address. + +So, the math for finding a byte offset is the same as Mode 3 (since they're both +a 2d grid). If the byte offset is EVEN it'll be the high bits of the `u16` at +half the byte offset rounded down. If the offset is ODD it'll be the low bits of +the `u16` at half the byte. + +Does that make sense? + +* If we want to write pixel (0,0) the byte offset is 0, so we change the high + bits of `u16` offset 0. Then we want to write to (1,0), so the byte offset is + 1, so we change the low bits of `u16` offset 0. The pixels are next to each + other, and the target bytes are next to each other, good so far. +* If we want to write to (5,6) that'd be byte `5 + 6 * 240 = 1445`, so we'd + target the low bits of `u16` offset `floor(1445/2) = 722`. + +As you can see, trying to write individual pixels in Mode 4 is mostly a bad +time. Fret not! We don't _have_ to write individual bytes. If our data is +arranged correctly ahead of time we can just write `u16` or `u32` values +directly. The video hardware doesn't care, it'll get along just fine. + +## Mode 5 + +Mode 5 is also a two page mode, but instead of compressing the size of a pixel's +data to fit in two pages, we compress the resolution. + +Mode 5 is full `u16` color, but only 160w x 128h per page. + +## In Conclusion... + +So what got written into VRAM in `hello1`? + +```rust + (0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F); + (0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0); + (0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00); +``` + +So at pixels `(120,80)`, `(136,80)`, and `(120,96)` we write three values. Once +again we probably need to [convert them](https://www.wolframalpha.com/) into +binary to make sense of it. + +* 0x001F: 0b11111 +* 0x03E0: 0b11111_00000 +* 0x7C00: 0b11111_00000_00000 + +Ah, of course, a red pixel, a green pixel, and a blue pixel. diff --git a/book/src/ch2/index.md b/book/src/ch2/index.md new file mode 100644 index 0000000..1263ced --- /dev/null +++ b/book/src/ch2/index.md @@ -0,0 +1,22 @@ +# Ch 2: User Input + +It's all well and good to draw three pixels, but they don't do anything yet. We +want them to do something, and for that we need to get some input from the user. + +The GBA, as I'm sure you know, has an arrow pad, A and B, L and R, Start and +Select. That's a little more than the NES/GB/CGB had, and a little less than the +SNES had. As you can guess, we get key state info from an IO register. + +Also, we will need a way to keep the program from running "too fast". On a +modern computer or console you do this with vsync info from the GPU and Monitor, +and on the GBA we'll be using vsync info from an IO register that tracks what +the display hardware is doing. + +As a way to apply our knowledge We'll make a simple "light cycle" game where +your dot leaves a trail behind them and you die if you go off the screen or if +you touch your own trail. We just make a copy of `hello2.rs` named +`light_cycle.rs` and then fill it in as we go through the chapter. Normally you +might not place the entire program into a single source file, particularly as it +grows over time, but since these are small examples it's much better to have +them be completely self contained than it is to have them be "properly +organized" for the long term. diff --git a/book/src/ch2/light_cycle.md b/book/src/ch2/light_cycle.md new file mode 100644 index 0000000..79ee4d6 --- /dev/null +++ b/book/src/ch2/light_cycle.md @@ -0,0 +1,119 @@ +# light_cycle + +Now let's make a game of "light_cycle" with our new knowledge. + +## Gameplay + +`light_cycle` is pretty simple, and very obvious if you've ever seen Tron. The +player moves around the screen with a trail left behind them. They die if they +go off the screen or if they touch their own trail. + +## Operations + +We need some better drawing operations this time around. + +```rust +pub unsafe fn mode3_clear_screen(color: u16) { + let color = color as u32; + let bulk_color = color << 16 | color; + let mut ptr = VRAM as *mut u32; + for _ in 0..SCREEN_HEIGHT { + for _ in 0..(SCREEN_WIDTH / 2) { + ptr.write_volatile(bulk_color); + ptr = ptr.offset(1); + } + } +} + +pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) { + (VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write_volatile(color); +} + +pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 { + (VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read_volatile() +} +``` + +The draw pixel and read pixel are both pretty obvious. What's new is the clear +screen operation. It changes the `u16` color into a `u32` and then packs the +value in twice. Then we write out `u32` values the whole way through screen +memory. This means we have to do less write operations overall, and so the +screen clear is twice as fast. + +Now we just have to fill in the main function: + +```rust +#[start] +fn main(_argc: isize, _argv: *const *const u8) -> isize { + unsafe { + DISPCNT.write_volatile(MODE3 | BG2); + } + + let mut px = SCREEN_WIDTH / 2; + let mut py = SCREEN_HEIGHT / 2; + let mut color = rgb16(31, 0, 0); + + loop { + // read the input for this frame + let this_frame_keys = read_key_input(); + + // adjust game state and wait for vblank + px += 2 * this_frame_keys.column_direction() as isize; + py += 2 * this_frame_keys.row_direction() as isize; + wait_until_vblank(); + + // draw the new game and wait until the next frame starts. + unsafe { + if px < 0 || py < 0 || px == SCREEN_WIDTH || py == SCREEN_HEIGHT { + // out of bounds, reset the screen and position. + mode3_clear_screen(0); + color = color.rotate_left(5); + px = SCREEN_WIDTH / 2; + py = SCREEN_HEIGHT / 2; + } else { + let color_here = mode3_read_pixel(px, py); + if color_here != 0 { + // crashed into our own line, reset the screen + mode3_clear_screen(0); + color = color.rotate_left(5); + } else { + // draw the new part of the line + mode3_draw_pixel(px, py, color); + mode3_draw_pixel(px, py + 1, color); + mode3_draw_pixel(px + 1, py, color); + mode3_draw_pixel(px + 1, py + 1, color); + } + } + } + wait_until_vdraw(); + } +} +``` + +Oh that's a lot more than before! + +First we set Mode 3 and Background 2, we know about that. + +Then we're going to store the player's x and y, along with a color value for +their light cycle. Then we enter the core loop. + +We read the keys for input, and then do as much as we can without touching video +memory. Since we're using video memory as the place to store the player's light +trail, we can't do much, we just update their position and wait for VBlank to +start. The player will be a 2x2 square, so the arrows will move you 2 pixels per +frame. + +Once we're in VBlank we check to see what kind of drawing we're doing. If the +player has gone out of bounds, we clear the screen, rotate their color, and then +reset their position. Why rotate the color? Just because it's fun to have +different colors. + +Next, if the player is in bounds we read the video memory for their position. If +it's not black that means we've been here before and the player has crashed into +their own line. In this case, we reset the game without moving them to a new +location. + +Finally, if the player is in bounds and they haven't crashed, we write their color into memory at this position. + +Regardless of how it worked out, we hold here until vdraw starts before going to +the next loop. diff --git a/book/src/ch2/the_key_input_register.md b/book/src/ch2/the_key_input_register.md new file mode 100644 index 0000000..656ae1e --- /dev/null +++ b/book/src/ch2/the_key_input_register.md @@ -0,0 +1,211 @@ +# The Key Input Register + +The Key Input Register is our next IO register. Its shorthand name is +[KEYINPUT](http://problemkaputt.de/gbatek.htm#gbakeypadinput) and it's a `u16` +at `0x4000130`. The entire register is obviously read only, you can't tell the +GBA what buttons are pressed. + +Each button is exactly one bit: + +| Bit | Button | +|:---:|:------:| +| 0 | A | +| 1 | B | +| 2 | Select | +| 3 | Start | +| 4 | Right | +| 5 | Left | +| 6 | Up | +| 7 | Down | +| 8 | R | +| 9 | L | + +The higher bits above are not used at all. + +Similar to other old hardware devices, the convention here is that a button's +bit is **clear when pressed, active when released**. In other words, when the +user is not touching the device at all the KEYINPUT value will read +`0b0000_0011_1111_1111`. There's similar values for when the user is pressing as +many buttons as possible, but since the left/right and up/down keys are on an +arrow pad the value can never be 0 since you can't ever press every single key +at once. + +When dealing with key input, the register always shows the exact key values at +any moment you read it. Obviously that's what it should do, but what it means to +you as a programmer is that you should usually gather input once at the top of a +game frame and then use that single input poll as the input values across the +whole game frame. + +Of course, you might want to know if a user's key state changed from frame to +frame. That's fairly easy too: We just store the last frame keys as well as the +current frame keys (it's only a `u16`) and then we can xor the two values. +Anything that shows up in the xor result is a key that changed. If it's changed +and it's now down, that means it was pushed this frame. If it's changed and it's +now up, that means it was released this frame. + +The other major thing you might frequently want is to know "which way" the arrow +pad is pointing: Up/Down/None and Left/Right/None. Sounds like an enum to me. +Except that often time we'll have situations where the direction just needs to +be multiplied by a speed and applied as a delta to a position. We want to +support that as well as we can too. + +## Key Input Code + +Let's get down to some code. First we want to make a way to read the address as +a `u16` and then wrap that in our newtype which will implement methods for +reading and writing the key bits. + +```rust +pub const KEYINPUT: *mut u16 = 0x400_0130 as *mut u16; + +/// A newtype over the key input state of the GBA. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct KeyInputSetting(u16); + +pub fn read_key_input() -> KeyInputSetting { + unsafe { KeyInputSetting(KEYINPUT.read_volatile()) } +} +``` + +Now we want a way to check if a key is _being pressed_, since that's normally +how we think of things as a game designer and even as a player. That is, usually +you'd say "if you press A, then X happens" instead of "if you don't press A, +then X does not happen". + +Normally we'd pick a constant for the bit we want, `&` it with our value, and +then check for `val != 0`. Since the bit we're looking for is `0` in the "true" +state we still pick the same constant and we still do the `&`, but we test with +`== 0`. Practically the same, right? Well, since I'm asking a rhetorical +question like that you can probably already guess that it's not the same. I was +shocked to learn this too. + +All we have to do is ask our good friend +[Godbolt](https://rust.godbolt.org/z/d-8oCe) what's gonna happen when the code +compiles. The link there has the page set for the `stable` 1.30 compiler just so +that the link results stay consistent if you read this book in a year or +something. Also, we've set the target to `thumbv6m-none-eabi`, which is a +slightly later version of ARM than the actual GBA, but it's close enough for +just checking. Of course, in a full program small functions like these will +probably get inlined into the calling code and disappear entirely as they're +folded and refolded by the compiler, but we can just check. + +It turns out that the `!=0` test is 4 instructions and the `==0` test is 6 +instructions. Since we want to get savings where we can, and we'll probably +check the keys of an input often enough, we'll just always use a `!=0` test and +then adjust how we initially read the register to compensate. + +```rust +pub fn read_key_input() -> KeyInputSetting { + unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b1111_1111_1111_1111) } +} +``` + +Now we add a method for seeing if a key is pressed. In the full library there's +a more advanced version of this that's built up via macro, but for this example +we'll just name a bunch of `const` values and then have a method that takes a +value and says if that bit is on. + +```rust +pub const KEY_A: u16 = 1 << 0; +pub const KEY_B: u16 = 1 << 1; +pub const KEY_SELECT: u16 = 1 << 2; +pub const KEY_START: u16 = 1 << 3; +pub const KEY_RIGHT: u16 = 1 << 4; +pub const KEY_LEFT: u16 = 1 << 5; +pub const KEY_UP: u16 = 1 << 6; +pub const KEY_DOWN: u16 = 1 << 7; +pub const KEY_R: u16 = 1 << 8; +pub const KEY_L: u16 = 1 << 9; + +impl KeyInputSetting { + pub fn contains(&self, key: u16) -> bool { + (self.0 & key) != 0 + } +} +``` + +Because each key is a unique bit you can even check for more than one key at +once by just adding two key values together. + +```rust +let input_contains_a_and_l = input.contains(KEY_A + KEY_L); +``` + +And we wanted to save the state of an old frame and compare it to the current +frame to see what was different: + +```rust + pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting { + KeyInputSetting(self.0 ^ other.0) + } +``` + +Anything that's "in" the difference output is a key that _changed_, and then if +the key reads as pressed this frame that means it was just pressed. The exact +mechanics of all the ways you might care to do something based on new key +presses is obviously quite varied, but it might be something like this: + +```rust +let this_frame_diff = this_frame_input.difference(last_frame_input); + +if this_frame_diff.contains(KEY_B) && this_frame_input.contains(KEY_B) { + // the user just pressed B, react in some way +} +``` + +And for the arrow pad, we'll make an enum that easily casts into `i32`. Whenever +we're working with stuff we can try to use `i32` / `isize` as often as possible +just because it's easier on the GBA's CPU if we stick to its native number size. +Having it be an enum lets us use `match` and be sure that we've covered all our +cases. + +```rust +/// A "tribool" value helps us interpret the arrow pad. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +#[repr(i32)] +pub enum TriBool { + Minus = -1, + Neutral = 0, + Plus = +1, +} +``` + +Now, how do we determine _which way_ is plus or minus? Well... I don't know. +Really. I'm not sure what the best one is because the GBA really wants the +origin at 0,0 with higher rows going down and higher cols going right. On the +other hand, all the normal math you and I learned in school is oriented with +increasing Y being upward on the page. So, at least for this demo, we're going +to go with what the GBA wants us to do and give it a try. If we don't end up +confusing ourselves then we can stick with that. Maybe we can cover it over +somehow later on. + +```rust + pub fn column_direction(&self) -> TriBool { + if self.contains(KEY_RIGHT) { + TriBool::Plus + } else if self.contains(KEY_LEFT) { + TriBool::Minus + } else { + TriBool::Neutral + } + } + + pub fn row_direction(&self) -> TriBool { + if self.contains(KEY_DOWN) { + TriBool::Plus + } else if self.contains(KEY_UP) { + TriBool::Minus + } else { + TriBool::Neutral + } + } +``` + +So then in our game, every frame we can check for `column_direction` and +`row_direction` and then apply those to the player's current position to make +them move around the screen. + +With that settled I think we're all done with user input for now. There's some +other things to eventually know about like key interrupts that you can set and +stuff, but we'll cover that later on because it's not necessary right now. diff --git a/book/src/ch2/the_vcount_register.md b/book/src/ch2/the_vcount_register.md new file mode 100644 index 0000000..3a427b9 --- /dev/null +++ b/book/src/ch2/the_vcount_register.md @@ -0,0 +1,71 @@ +# The VCount Register + +There's an IO register called +[VCOUNT](http://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus) that shows +you, what else, the Vertical (row) COUNT(er). It's a `u16` at address +`0x0400_0006`, and it's how we'll be doing our very poor quality vertical sync +code to start. + +* **What makes it poor?** Well, we're just going to read from the vcount value as + often as possible every time we need to wait for a specific value to come up, + and then proceed once it hits the point we're looking for. +* **Why is this bad?** Because we're making the CPU do a lot of useless work, + which uses a lot more power that necessary. Even if you're not on an actual + GBA you might be running inside an emulator on a phone or other handheld. You + wanna try to save battery if all you're doing with that power use is waiting + instead of making a game actually do something. +* **Can we do better?** We can, but not yet. The better way to do things is to + use a BIOS call to put the CPU into low power mode until a VBlank interrupt + happens. However, we don't know about interrupts yet, and we don't know about + BIOS calls yet, so we'll do the basic thing for now and then upgrade later. + +So the way that display hardware actually displays each frame is that it moves a +tiny pointer left to right across each pixel row one pixel at a time. When it's +within the actual screen width (240px) it's drawing out those pixels. Then it +goes _past_ the edge of the screen for 68px during a period known as the +"horizontal blank" (HBlank). Then it starts on the next row and does that loop +over again. This happens for the whole screen height (160px) and then once again +it goes past the last row for another 68px into a "vertical blank" (VBlank) +period. + +* One pixel is 4 CPU cycles +* HDraw is 240 pixels, HBlank is 68 pixels (1,232 cycles per full scanline) +* VDraw is 150 scanlines, VBlank is 68 scanlines (280,896 cycles per full refresh) + +Now you may remember some stuff from the display control register section where +it was mentioned that some parts of memory are best accessed during VBlank, and +also during hblank with a setting applied. These blanking periods are what was +being talked about. At other times if you attempt to access video or object +memory you (the CPU) might try touching the same memory that the display device +is trying to use, in which case you get bumped back a cycle so that the display +can finish what it's doing. Also, if you really insist on doing video memory +changes while the screen is being drawn then you might get some visual glitches. +If you can, just prepare all your changes ahead of time and then assign then all +quickly during the blank period. + +So first we want a way to check the vcount value at all: + +```rust +pub const VCOUNT: *mut u16 = 0x0400_0006 as *mut u16; + +pub fn read_vcount() -> u16 { + unsafe { VCOUNT.read_volatile() } +} +``` + +Then we want two little helper functions to wait until VBlank and vdraw. + +```rust +pub const SCREEN_HEIGHT: isize = 160; + +pub fn wait_until_vblank() { + while read_vcount() < SCREEN_HEIGHT as u16 {} +} + +pub fn wait_until_vdraw() { + while read_vcount() >= SCREEN_HEIGHT as u16 {} +} +``` + +And... that's it. No special types to be made this time around, it's just a +number we read out of memory. diff --git a/book/src/introduction.md b/book/src/introduction.md new file mode 100644 index 0000000..920d9f8 --- /dev/null +++ b/book/src/introduction.md @@ -0,0 +1,28 @@ +# Introduction + +Here's a book that'll help you program in Rust on the GBA. + +It's very "work in progress". At the moment there's only one demo program. + +## Getting Help + +If you want to contact us you should join the [Rust Community +Discord](https://discordapp.com/invite/aVESxV8) and ask in the `#gamedev` +channel. `Ketsuban` is the wizard who knows much more about how it all works, +and `Lokathor` is the fool who decided to write a crate and book for it. + +If it's _not_ a GBA specific question then you can also ask any of the few +hundred other folks in the server as well. + +## Other Works + +If you want to read more about developing on the GBA there are some other good resources as well: + +* [Tonc](https://www.coranac.com/tonc/text/toc.htm), a tutorial series written + for C, but it's what I based the ordering of this book's sections on. +* [GBATEK](http://problemkaputt.de/gbatek.htm), a homebrew tech manual for + GBA/NDS/DSi. We will regularly link to parts of it when talking about various + bits of the GBA. +* [CowBite](https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm) is another + tech specification that's more GBA specific. It's sometimes got more ASCII + art diagrams and example C struct layouts than GBATEK does. diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..739d3f8 --- /dev/null +++ b/build.bat @@ -0,0 +1,14 @@ + +@rem Build the crt0 file before we begin +arm-none-eabi-as crt0.s -o crt0.o + +@rem Build all examples, both debug and release +cargo xbuild --examples --target thumbv4-none-agb.json +cargo xbuild --examples --target thumbv4-none-agb.json --release + +@echo Packing examples into ROM files... +@for %%I in (.\examples\*.*) do @( + echo %%~nI + arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/release/examples/%%~nI target/example-%%~nI.gba >nul + gbafix target/example-%%~nI.gba >nul +) diff --git a/crt0.s b/crt0.s new file mode 100644 index 0000000..9b06b47 --- /dev/null +++ b/crt0.s @@ -0,0 +1,34 @@ + .arm +__start: + b .Linit + + @ ROM header will be filled in by gbafix + .fill 188, 1, 0 + +.Linit: + @ set IRQ stack pointer + mov r0, #0x12 + msr CPSR_cf, r0 + ldr sp, =0x3007fa0 + + @ set user stack pointer + mov r0, #0x1f + msr CPSR_cf, r0 + ldr sp, =0x3007f00 + + @ copy .data section to IWRAM + ldr r0, =__data_lma @ source address + ldr r1, =__data_start @ destination address + ldr r3, =__data_end + sub r2, r3, r1 + beq .Lskip @ don't try to copy an empty .data section + add r2, #3 + mov r2, r2, asr #2 @ length (in words) + add r2, #0x04000000 @ copy by words + swi 0xb0000 + +.Lskip: + @ jump to user code + ldr r0, =main + bx r0 + .pool diff --git a/.nojekyll b/docs/.nojekyll similarity index 100% rename from .nojekyll rename to docs/.nojekyll diff --git a/FontAwesome/css/font-awesome.css b/docs/FontAwesome/css/font-awesome.css similarity index 100% rename from FontAwesome/css/font-awesome.css rename to docs/FontAwesome/css/font-awesome.css diff --git a/FontAwesome/fonts/FontAwesome.ttf b/docs/FontAwesome/fonts/FontAwesome.ttf similarity index 100% rename from FontAwesome/fonts/FontAwesome.ttf rename to docs/FontAwesome/fonts/FontAwesome.ttf diff --git a/FontAwesome/fonts/fontawesome-webfont.eot b/docs/FontAwesome/fonts/fontawesome-webfont.eot similarity index 100% rename from FontAwesome/fonts/fontawesome-webfont.eot rename to docs/FontAwesome/fonts/fontawesome-webfont.eot diff --git a/FontAwesome/fonts/fontawesome-webfont.svg b/docs/FontAwesome/fonts/fontawesome-webfont.svg similarity index 100% rename from FontAwesome/fonts/fontawesome-webfont.svg rename to docs/FontAwesome/fonts/fontawesome-webfont.svg diff --git a/FontAwesome/fonts/fontawesome-webfont.ttf b/docs/FontAwesome/fonts/fontawesome-webfont.ttf similarity index 100% rename from FontAwesome/fonts/fontawesome-webfont.ttf rename to docs/FontAwesome/fonts/fontawesome-webfont.ttf diff --git a/FontAwesome/fonts/fontawesome-webfont.woff b/docs/FontAwesome/fonts/fontawesome-webfont.woff similarity index 100% rename from FontAwesome/fonts/fontawesome-webfont.woff rename to docs/FontAwesome/fonts/fontawesome-webfont.woff diff --git a/FontAwesome/fonts/fontawesome-webfont.woff2 b/docs/FontAwesome/fonts/fontawesome-webfont.woff2 similarity index 100% rename from FontAwesome/fonts/fontawesome-webfont.woff2 rename to docs/FontAwesome/fonts/fontawesome-webfont.woff2 diff --git a/ayu-highlight.css b/docs/ayu-highlight.css similarity index 100% rename from ayu-highlight.css rename to docs/ayu-highlight.css diff --git a/book.js b/docs/book.js similarity index 100% rename from book.js rename to docs/book.js diff --git a/ch0/index.html b/docs/ch0/index.html similarity index 100% rename from ch0/index.html rename to docs/ch0/index.html diff --git a/ch1/hello1.html b/docs/ch1/hello1.html similarity index 100% rename from ch1/hello1.html rename to docs/ch1/hello1.html diff --git a/ch1/hello2.html b/docs/ch1/hello2.html similarity index 100% rename from ch1/hello2.html rename to docs/ch1/hello2.html diff --git a/ch1/index.html b/docs/ch1/index.html similarity index 100% rename from ch1/index.html rename to docs/ch1/index.html diff --git a/ch1/io_registers.html b/docs/ch1/io_registers.html similarity index 100% rename from ch1/io_registers.html rename to docs/ch1/io_registers.html diff --git a/ch1/the_display_control_register.html b/docs/ch1/the_display_control_register.html similarity index 100% rename from ch1/the_display_control_register.html rename to docs/ch1/the_display_control_register.html diff --git a/ch1/video_memory_intro.html b/docs/ch1/video_memory_intro.html similarity index 100% rename from ch1/video_memory_intro.html rename to docs/ch1/video_memory_intro.html diff --git a/ch2/index.html b/docs/ch2/index.html similarity index 100% rename from ch2/index.html rename to docs/ch2/index.html diff --git a/ch2/light_cycle.html b/docs/ch2/light_cycle.html similarity index 100% rename from ch2/light_cycle.html rename to docs/ch2/light_cycle.html diff --git a/ch2/the_key_input_register.html b/docs/ch2/the_key_input_register.html similarity index 100% rename from ch2/the_key_input_register.html rename to docs/ch2/the_key_input_register.html diff --git a/ch2/the_vcount_register.html b/docs/ch2/the_vcount_register.html similarity index 100% rename from ch2/the_vcount_register.html rename to docs/ch2/the_vcount_register.html diff --git a/clipboard.min.js b/docs/clipboard.min.js similarity index 100% rename from clipboard.min.js rename to docs/clipboard.min.js diff --git a/css/chrome.css b/docs/css/chrome.css similarity index 100% rename from css/chrome.css rename to docs/css/chrome.css diff --git a/css/general.css b/docs/css/general.css similarity index 100% rename from css/general.css rename to docs/css/general.css diff --git a/css/print.css b/docs/css/print.css similarity index 100% rename from css/print.css rename to docs/css/print.css diff --git a/css/variables.css b/docs/css/variables.css similarity index 100% rename from css/variables.css rename to docs/css/variables.css diff --git a/elasticlunr.min.js b/docs/elasticlunr.min.js similarity index 100% rename from elasticlunr.min.js rename to docs/elasticlunr.min.js diff --git a/favicon.png b/docs/favicon.png similarity index 100% rename from favicon.png rename to docs/favicon.png diff --git a/highlight.css b/docs/highlight.css similarity index 100% rename from highlight.css rename to docs/highlight.css diff --git a/highlight.js b/docs/highlight.js similarity index 100% rename from highlight.js rename to docs/highlight.js diff --git a/index.html b/docs/index.html similarity index 95% rename from index.html rename to docs/index.html index e749554..65558e1 100644 --- a/index.html +++ b/docs/index.html @@ -139,13 +139,6 @@

Introduction

Here's a book that'll help you program in Rust on the GBA.

It's very "work in progress". At the moment there's only one demo program.

-

Getting Help

-

If you want to contact us you should join the Rust Community -Discord and ask in the #gamedev -channel. Ketsuban is the wizard who knows much more about how it all works, -and Lokathor is the fool who decided to write a crate and book for it.

-

If it's not a GBA specific question then you can also ask any of the few -hundred other folks in the server as well.

Other Works

If you want to read more about developing on the GBA there are some other good resources as well: