mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-24 11:11:31 +11:00
2375 lines
127 KiB
HTML
2375 lines
127 KiB
HTML
<!DOCTYPE HTML>
|
|
<html lang="en" class="sidebar-visible no-js">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>Rust GBA Guide</title>
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
<meta name="description" content="">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
<link rel="shortcut icon" href="favicon.png">
|
|
<link rel="stylesheet" href="css/variables.css">
|
|
<link rel="stylesheet" href="css/general.css">
|
|
<link rel="stylesheet" href="css/chrome.css">
|
|
<link rel="stylesheet" href="css/print.css" media="print">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
|
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="highlight.css">
|
|
<link rel="stylesheet" href="tomorrow-night.css">
|
|
<link rel="stylesheet" href="ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
|
|
|
|
</head>
|
|
<body class="light">
|
|
<!-- Provide site root to javascript -->
|
|
<script type="text/javascript">var path_to_root = "";</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script type="text/javascript">
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script type="text/javascript">
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = 'light'; }
|
|
document.body.className = theme;
|
|
document.querySelector('html').className = theme + ' js';
|
|
</script>
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script type="text/javascript">
|
|
var html = document.querySelector('html');
|
|
var sidebar = 'hidden';
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
}
|
|
html.classList.remove('sidebar-visible');
|
|
html.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<ol class="chapter"><li><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li><a href="ch00/index.html"><strong aria-hidden="true">2.</strong> Ch 0: Development Setup</a></li><li><a href="ch01/index.html"><strong aria-hidden="true">3.</strong> Ch 1: Hello GBA</a></li><li><ol class="section"><li><a href="ch01/hello1.html"><strong aria-hidden="true">3.1.</strong> hello1</a></li><li><a href="ch01/volatile.html"><strong aria-hidden="true">3.2.</strong> Volatile</a></li><li><a href="ch01/io_registers.html"><strong aria-hidden="true">3.3.</strong> IO Registers</a></li><li><a href="ch01/the_display_control_register.html"><strong aria-hidden="true">3.4.</strong> The Display Control Register</a></li><li><a href="ch01/video_memory_intro.html"><strong aria-hidden="true">3.5.</strong> Video Memory Intro</a></li><li><a href="ch01/hello2.html"><strong aria-hidden="true">3.6.</strong> hello2</a></li></ol></li><li><a href="ch02/index.html"><strong aria-hidden="true">4.</strong> Ch 2: User Input</a></li><li><ol class="section"><li><a href="ch02/the_key_input_register.html"><strong aria-hidden="true">4.1.</strong> The Key Input Register</a></li><li><a href="ch02/the_vcount_register.html"><strong aria-hidden="true">4.2.</strong> The VCount Register</a></li><li><a href="ch02/light_cycle.html"><strong aria-hidden="true">4.3.</strong> light_cycle</a></li></ol></li><li><a href="ch03/index.html"><strong aria-hidden="true">5.</strong> Ch 3: Memory and Objects</a></li><li><ol class="section"><li><a href="ch03/gba_memory_mapping.html"><strong aria-hidden="true">5.1.</strong> GBA Memory Mapping</a></li><li><a href="ch03/tile_data.html"><strong aria-hidden="true">5.2.</strong> Tile Data</a></li><li><a href="ch03/regular_backgrounds.html"><strong aria-hidden="true">5.3.</strong> Regular Backgrounds</a></li><li><a href="ch03/regular_objects.html"><strong aria-hidden="true">5.4.</strong> Regular Objects</a></li><li><a href="ch03/gba_rng.html"><strong aria-hidden="true">5.5.</strong> GBA RNG</a></li><li><a href="ch03/memory_game.html"><strong aria-hidden="true">5.6.</strong> memory_game</a></li></ol></li></ol>
|
|
</nav>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
|
|
<div id="menu-bar" class="menu-bar">
|
|
<div id="menu-bar-sticky-container">
|
|
<div class="left-buttons">
|
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light <span class="default">(default)</span></button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<h1 class="menu-title">Rust GBA Guide</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="print.html" title="Print this book" aria-label="Print this book">
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script type="text/javascript">
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<a class="header" href="#introduction" id="introduction"><h1>Introduction</h1></a>
|
|
<p>Here's a book that'll help you program in Rust on the Game Boy Advance (GBA).</p>
|
|
<p>It's a work in progress of course, but so is most of everything in Rust.</p>
|
|
<a class="header" href="#style-and-purpose" id="style-and-purpose"><h2>Style and Purpose</h2></a>
|
|
<p>I'm out to teach you how to program in Rust on the GBA, obviously. However,
|
|
while there <em>is</em> a <a href="https://github.com/rust-console/gba">gba</a> crate, and while I
|
|
genuinely believe it to be a good and useful crate for GBA programming, we <em>will
|
|
not</em> be using the <code>gba</code> crate within this book. In fact we won't be using any
|
|
crates at all. We can call it the <a href="https://handmadehero.org/">Handmade Hero</a>
|
|
approach, if you like.</p>
|
|
<p>I don't want to just teach you how to use the <code>gba</code> crate, I want to teach you
|
|
what you'd need to know to write the crate from scratch if it wasn't there.</p>
|
|
<p>Each chapter of the book will focus on a few things you'll need to know about
|
|
GBA programming and then present a fully self-contained example that puts those
|
|
ideas into action. Just one file per example, no dependencies, no external
|
|
assets, no fuss. The examples will be in the text of the book within code
|
|
blocks, but also you can find them in the <a href="https://github.com/rust-console/gba/tree/master/examples">examples
|
|
directory</a> of the repo
|
|
if you want to get them that way.</p>
|
|
<a class="header" href="#expected-knowledge" id="expected-knowledge"><h2>Expected Knowledge</h2></a>
|
|
<p>I will try not to ask too much of the reader ahead of time, but you are expected
|
|
to have already read <a href="https://doc.rust-lang.org/book/">The Rust Book</a>. Having
|
|
also read through the <a href="https://doc.rust-lang.org/nomicon/">Rustonomicon</a> is
|
|
appreciated but not required.</p>
|
|
<p>It's very difficult to know when you've said something that someone else won't
|
|
already know about, or if you're presenting ideas out of order. If things aren't
|
|
clear please <a href="https://github.com/rust-console/gba/issues">file an issue</a> and
|
|
we'll try to address it.</p>
|
|
<a class="header" href="#getting-help" id="getting-help"><h2>Getting Help</h2></a>
|
|
<p>If you want to contact us you should join the <a href="https://discordapp.com/invite/aVESxV8">Rust Community
|
|
Discord</a> and ask in the <code>#gamedev</code>
|
|
channel.</p>
|
|
<ul>
|
|
<li><code>Ketsuban</code> is the wizard who knows much more about how it all works</li>
|
|
<li><code>Lokathor</code> is the fool who decided to write a crate and book for it.</li>
|
|
</ul>
|
|
<p>If it's <em>not</em> a GBA specific question then you can probably ask any of the other
|
|
folks in the server as well (there's a few hundred folks).</p>
|
|
<a class="header" href="#further-reading" id="further-reading"><h2>Further Reading</h2></a>
|
|
<p>If you want to read more about developing on the GBA there are some other good
|
|
resources as well:</p>
|
|
<ul>
|
|
<li><a href="https://www.coranac.com/tonc/text/toc.htm">TONC</a>, a tutorial series written
|
|
for C, but it's what I based the ordering of this book's sections on.</li>
|
|
<li><a href="http://problemkaputt.de/gbatek.htm">GBATEK</a>, a homebrew tech manual for
|
|
GBA/NDS/DSi. We will regularly link to parts of it when talking about various
|
|
bits of the GBA.</li>
|
|
</ul>
|
|
<a class="header" href="#chapter-0-development-setup" id="chapter-0-development-setup"><h1>Chapter 0: Development Setup</h1></a>
|
|
<p>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.</p>
|
|
<p>Once again, extra special thanks to <strong>Ketsuban</strong>, who first dove into how to
|
|
make this all work with rust and then shared it with the world.</p>
|
|
<a class="header" href="#per-system-setup" id="per-system-setup"><h2>Per System Setup</h2></a>
|
|
<p>Obviously you need your computer to have a <a href="https://rustup.rs/">working rust
|
|
installation</a>. However, you'll also need to ensure that
|
|
you're using a nightly toolchain (we will need it for inline assembly, among
|
|
other potential useful features). You can run <code>rustup default nightly</code> to set
|
|
nightly as the system wide default toolchain, or you can use a <a href="https://github.com/rust-lang-nursery/rustup.rs#the-toolchain-file">toolchain
|
|
file</a> to use
|
|
nightly just on a specific project, but either way we'll be assuming the use of
|
|
nightly from now on. You'll also need the <code>rust-src</code> component so that
|
|
<code>cargo-xbuild</code> will be able to compile the core crate for us in a bit, so run
|
|
<code>rustup component add rust-src</code>.</p>
|
|
<p>Next, you need <a href="https://devkitpro.org/wiki/Getting_Started">devkitpro</a>. They've
|
|
got a graphical installer for Windows that runs nicely, and I guess <code>pacman</code>
|
|
support on Linux (I'm on Windows so I haven't tried the Linux install myself).
|
|
We'll be using a few of their general binutils for the <code>arm-none-eabi</code> target,
|
|
and we'll also be using some of their tools that are specific to GBA
|
|
development, so <em>even if</em> you already have the right binutils for whatever
|
|
reason, you'll still want devkitpro for the <code>gbafix</code> utility.</p>
|
|
<ul>
|
|
<li>On Windows you'll want something like <code>C:\devkitpro\devkitARM\bin</code> and
|
|
<code>C:\devkitpro\tools\bin</code> to be <a href="https://stackoverflow.com/q/44272416/455232">added to your
|
|
PATH</a>, depending on where you
|
|
installed it to and such.</li>
|
|
<li>On Linux you can use pacman to get it, and the default install puts the stuff
|
|
in <code>/opt/devkitpro/devkitARM/bin</code> and <code>/opt/devkitpro/tools/bin</code>. If you need
|
|
help you can look in our repository's
|
|
<a href="https://github.com/rust-console/gba/blob/master/.travis.yml">.travis.yml</a>
|
|
file to see exactly what our CI does.</li>
|
|
</ul>
|
|
<p>Finally, you'll need <code>cargo-xbuild</code>. Just run <code>cargo install cargo-xbuild</code> and
|
|
cargo will figure it all out for you.</p>
|
|
<a class="header" href="#per-project-setup" id="per-project-setup"><h2>Per Project Setup</h2></a>
|
|
<p>Once the system wide tools are ready, you'll need some particular files each
|
|
time you want to start a new project. You can find them in the root of the
|
|
<a href="https://github.com/rust-console/gba">rust-console/gba repo</a>.</p>
|
|
<ul>
|
|
<li><code>thumbv4-none-agb.json</code> describes the overall GBA to cargo-xbuild (and LLVM)
|
|
so it knows what to do. Technically the GBA is <code>thumbv4-none-eabi</code>, but we
|
|
change the <code>eabi</code> to <code>agb</code> so that we can distinguish it from other <code>eabi</code>
|
|
devices when using <code>cfg</code> flags.</li>
|
|
<li><code>crt0.s</code> 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
|
|
<code>crt0.o</code> file before it can actually be used, but we'll cover that below.</li>
|
|
<li><code>linker.ld</code> tells the linker all the critical info about the layout
|
|
expectations that the GBA has about our program, and that it should also
|
|
include the <code>crt0.o</code> file with our compiled rust code.</li>
|
|
</ul>
|
|
<a class="header" href="#compiling" id="compiling"><h2>Compiling</h2></a>
|
|
<p>The next steps only work once you've got some source code to build. If you need
|
|
a quick test, copy the <code>hello1.rs</code> file from our examples directory in the
|
|
repository.</p>
|
|
<p>Once you've got something to build, you perform the following steps:</p>
|
|
<ul>
|
|
<li>
|
|
<p><code>arm-none-eabi-as crt0.s -o crt0.o</code></p>
|
|
<ul>
|
|
<li>This builds your text format <code>crt0.s</code> file into object format <code>crt0.o</code>. You
|
|
don't need to perform it every time, only when <code>crt0.s</code> changes, but you
|
|
might as well do it every time so that you never forget to because it's a
|
|
practically instant operation.</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<p><code>cargo xbuild --target thumbv4-none-agb.json</code></p>
|
|
<ul>
|
|
<li>This builds your Rust source. It accepts <em>most of</em> the normal options, such
|
|
as <code>--release</code>, and options, such as <code>--bin foo</code> or <code>--examples</code>, that you'd
|
|
expect <code>cargo</code> to accept.</li>
|
|
<li>You <strong>can not</strong> build and run tests this way, because they require <code>std</code>,
|
|
which the GBA doesn't have. If you want you can still run some of your
|
|
project's tests with <code>cargo test --lib</code> or similar, 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 <code>examples/</code> directory and then run the demo in an emulator
|
|
and see if it does what you expect.</li>
|
|
<li>The file extension is important. <code>cargo xbuild</code> 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.</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<p>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, <a href="https://mgba.io/2018/09/24/mgba-0.7-beta1/">mgba 0.7 beta
|
|
1</a> can do it, and perhaps other
|
|
emulators can also do it.</p>
|
|
<p>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.</p>
|
|
<ul>
|
|
<li>
|
|
<p><code>arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/MODE/BIN_NAME target/ROM_NAME.gba</code></p>
|
|
<ul>
|
|
<li>This will perform an <a href="https://linux.die.net/man/1/objcopy">objcopy</a> on our
|
|
program. Here I've named the program <code>arm-none-eabi-objcopy</code>, which is what
|
|
devkitpro calls their version of <code>objcopy</code> 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.</li>
|
|
<li>As you can see from reading the man page, the <code>-O binary</code> option takes our
|
|
lovely ELF file with symbols and all that and strips it down to basically a
|
|
bare memory dump of the program.</li>
|
|
<li>The next argument is the input file. You might not be familiar with how
|
|
<code>cargo</code> arranges stuff in the <code>target/</code> directory, and between RLS and
|
|
<code>cargo doc</code> and stuff it gets kinda crowded, so it goes like this:
|
|
<ul>
|
|
<li>Since our program was built for a non-local target, first we've got a
|
|
directory named for that target, <code>thumbv4-none-agb/</code></li>
|
|
<li>Next, the "MODE" is either <code>debug/</code> or <code>release/</code>, depending on if we had
|
|
the <code>--release</code> flag included. You'll probably only be packing release
|
|
mode programs all the way into GBA roms, but it works with either mode.</li>
|
|
<li>Finally, the name of the program. If your program is something out of the
|
|
project's <code>src/bin/</code> then it'll be that file's name, or whatever name you
|
|
configured for the bin in the <code>Cargo.toml</code> file. If your program is
|
|
something out of the project's <code>examples/</code> directory there will be a
|
|
similar <code>examples/</code> sub-directory first, and then the example's name.</li>
|
|
</ul>
|
|
</li>
|
|
<li>The final argument is the output of the <code>objcopy</code>, which I suggest putting
|
|
at just the top level of the <code>target/</code> directory. Really it could go
|
|
anywhere, but if you're using git then it's likely that your <code>.gitignore</code>
|
|
file is already setup to exclude everything in <code>target/</code>, so this makes sure
|
|
that your intermediate game builds don't get checked into your git.</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<p><code>gbafix target/ROM_NAME.gba</code></p>
|
|
<ul>
|
|
<li>The <code>gbafix</code> tool also comes from devkitpro. The GBA is very picky about a
|
|
ROM's format, and <code>gbafix</code> patches the ROM's header and such so that it'll
|
|
work right. Unlike <code>objcopy</code>, 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.</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<p>And you're finally done!</p>
|
|
<p>Of course, you probably want to make a script for all that, but it's up to you.
|
|
On our own project we have it mostly set up within a <code>Makefile.toml</code> which runs
|
|
using the <a href="https://github.com/sagiegurari/cargo-make">cargo-make</a> plugin. It's
|
|
not really the best plugin, but it's what's available.</p>
|
|
<a class="header" href="#ch-1-hello-gba" id="ch-1-hello-gba"><h1>Ch 1: Hello GBA</h1></a>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<a class="header" href="#hello1" id="hello1"><h1>hello1</h1></a>
|
|
<p>Our first example will be a totally minimal, full magic number crazy town.
|
|
Ready? Here goes:</p>
|
|
<p><code>hello1.rs</code></p>
|
|
<pre><pre class="playpen"><code class="language-rust">#![feature(start)]
|
|
#![no_std]
|
|
|
|
#[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 {}
|
|
}
|
|
}
|
|
</code></pre></pre>
|
|
<p>Throw that into your project skeleton, build the program (as described back in
|
|
Chapter 0), and give it a run in your emulator. 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 <a href="https://discordapp.com/invite/aVESxV8">Rust Community Discord</a>,
|
|
until you're able to get your three dots going.</p>
|
|
<a class="header" href="#a-basic-hello1-explanation" id="a-basic-hello1-explanation"><h2>A basic hello1 explanation</h2></a>
|
|
<p>So, what just happened? Even if you're used to Rust that might look pretty
|
|
strange. We'll go over most of the little parts right here, and then bigger
|
|
parts will get their own sections.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#![feature(start)]
|
|
#fn main() {
|
|
#}</code></pre></pre>
|
|
<p>This enables the <a href="https://doc.rust-lang.org/beta/unstable-book/language-features/start.html">start
|
|
feature</a>,
|
|
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 <a href="https://github.com/rust-lang/rust/issues/29633">tracking
|
|
issue</a>.</p>
|
|
<p>Basically, a GBA game is even more low-level than the <em>normal</em> amount of
|
|
low-level that you get from Rust, so we have to tell the compiler to account for
|
|
that by specifying a <code>#[start]</code>, and we need this feature on to do that.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#![no_std]
|
|
#fn main() {
|
|
#}</code></pre></pre>
|
|
<p>There's no standard library available on the GBA, so we'll have to live a core
|
|
only life.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[panic_handler]
|
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|
loop {}
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>This sets our <a href="https://doc.rust-lang.org/nightly/nomicon/panic-handler.html">panic
|
|
handler</a>.
|
|
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 <em>can't even return</em> from here, so we just sit in
|
|
an infinite loop. The player will have to reset the universe from the outside.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[start]
|
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
|
</code></pre></pre>
|
|
<p>This is our <code>#[start]</code>. We call it <code>main</code>, but it's not like a <code>main</code> that you'd
|
|
see in a Rust program. It's <em>more like</em> the sort of <code>main</code> that you'd see in a C
|
|
program, but it's still <strong>not</strong> that either. If you compile a <code>#[start]</code> program
|
|
for a target with an OS such as <code>arm-none-eabi-nm</code> you can open up the debug
|
|
info and see that your result will have the symbol for the C <code>main</code> along side
|
|
the symbol for the start <code>main</code> that we write here. Our start <code>main</code> is just its
|
|
own unique thing, and the inputs and outputs have to be like that because that's
|
|
how <code>#[start]</code> is specified to work in Rust.</p>
|
|
<p>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.</p>
|
|
<p>Side note: if you want to learn more about stuff "before main gets called" you
|
|
can watch a great <a href="https://www.youtube.com/watch?v=dOfucXtyEsU">CppCon talk</a> 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.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
unsafe {
|
|
#}</code></pre></pre>
|
|
<p>I hope you're all set for some <code>unsafe</code>, because there's a lot of it to be had.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
(0x04000000 as *mut u16).write_volatile(0x0403);
|
|
#}</code></pre></pre>
|
|
<p>Sure!</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
(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);
|
|
#}</code></pre></pre>
|
|
<p>Ah, of course.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
loop {}
|
|
}
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>And, as mentioned above, there's no place for a GBA program to "return to", so
|
|
we can't ever let <code>main</code> try to return there. Instead, we go into an infinite
|
|
<code>loop</code> that does nothing. The fact that this doesn't ever return an <code>isize</code>
|
|
value doesn't seem to bother Rust, because I guess we're at least not returning
|
|
any other type of thing instead.</p>
|
|
<p>Fun fact: unlike in C++, an infinite loop with no side effects isn't Undefined
|
|
Behavior for us rustaceans... <em>semantically</em>. In truth LLVM has a <a href="https://github.com/rust-lang/rust/issues/28728">known
|
|
bug</a> in this area, so we won't
|
|
actually be relying on empty loops in any future programs.</p>
|
|
<a class="header" href="#all-those-magic-numbers" id="all-those-magic-numbers"><h2>All Those Magic Numbers</h2></a>
|
|
<p>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.</p>
|
|
<ul>
|
|
<li><code>0x04000000</code> is the address of an IO Register called the Display Control.</li>
|
|
<li><code>0x06000000</code> is the start of Video RAM.</li>
|
|
</ul>
|
|
<p>So we write some magic to the display control register once, then we write some
|
|
other magic to three magic locations in the Video RAM. Somehow that shows three
|
|
dots. Gotta read on to find out why!</p>
|
|
<a class="header" href="#volatile" id="volatile"><h1>Volatile</h1></a>
|
|
<p>Before we focus on what the numbers mean, first let's ask ourselves: Why are we
|
|
doing <em>volatile</em> writes? You've probably never used that keywords before at all.
|
|
What <em>is</em> volatile anyway?</p>
|
|
<p>Well, the optimizer is pretty aggressive, 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 correct, but sometimes it's not.</p>
|
|
<p>Marking a read or write as <em>volatile</em> 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.</p>
|
|
<p>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</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
c.write_volatile(5);
|
|
a += b;
|
|
d.write_volatile(7);
|
|
#}</code></pre></pre>
|
|
<p>might end up changing <code>a</code> either before or after the change to <code>c</code> (since the
|
|
value of <code>a</code> doesn't affect the write to <code>c</code>), but the write to <code>d</code> will
|
|
<em>always</em> happen after the write to <code>c</code>, even though the compiler doesn't see any
|
|
direct data dependency there.</p>
|
|
<p>If you ever go on to 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
|
|
those sorts of thread safety concerns (there's interrupts, but that's another
|
|
matter).</p>
|
|
<a class="header" href="#volatile-by-default" id="volatile-by-default"><h2>Volatile by default</h2></a>
|
|
<p>Of course, writing out <code>volatile_write</code> every time is more than we wanna do.
|
|
There's clarity and then there's excessive. This is a chance to write our first
|
|
<a href="https://doc.rust-lang.org/1.0.0/style/features/types/newtype.html">newtype</a>.
|
|
Basically a type that's got the exact same binary representation as some other
|
|
type, but new methods and trait implementations.</p>
|
|
<p>We want a <code>*mut T</code> that's volatile by default, and also when we offset it...
|
|
well the verdict is slightly unclear on how <code>offset</code> vs <code>wrapping_offset</code> work
|
|
when you're using pointers that you made up out of nowhere. I've asked the
|
|
experts and they genuinely weren't sure, so we'll make an <code>offset</code> method that
|
|
does a <code>wrapping_offset</code> just to be careful.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
#[repr(transparent)]
|
|
pub struct VolatilePtr<T>(pub *mut T);
|
|
impl<T> VolatilePtr<T> {
|
|
pub unsafe fn read(&self) -> T {
|
|
core::ptr::read_volatile(self.0)
|
|
}
|
|
pub unsafe fn write(&self, data: T) {
|
|
core::ptr::write_volatile(self.0, data);
|
|
}
|
|
pub unsafe fn offset(self, count: isize) -> Self {
|
|
VolatilePtr(self.0.wrapping_offset(count))
|
|
}
|
|
}
|
|
#}</code></pre></pre>
|
|
<a class="header" href="#io-registers" id="io-registers"><h1>IO Registers</h1></a>
|
|
<p>The GBA has a large number of <strong>IO Registers</strong> (not to be confused with CPU
|
|
registers). These are special memory locations from <code>0x04000000</code> to
|
|
<code>0x040003FE</code>. GBATEK has a <a href="http://problemkaputt.de/gbatek.htm#gbaiomap">full
|
|
list</a>, but we only need to learn
|
|
about a few of them at a time as we go, so don't be worried.</p>
|
|
<p>The important facts to know about IO Registers are these:</p>
|
|
<ul>
|
|
<li>Each has their own specific size. Most are <code>u16</code>, but some are <code>u32</code>.</li>
|
|
<li>All of them must be accessed in a <code>volatile</code> style.</li>
|
|
<li>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.
|
|
<ul>
|
|
<li>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).</li>
|
|
<li>If you read from a write-only position, you get back values that are
|
|
<a href="http://problemkaputt.de/gbatek.htm#gbaunpredictablethings">basically
|
|
nonsense</a>. 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).</li>
|
|
<li>You can always check GBATEK to be sure, but if I don't mention it then a bit
|
|
is probably both read and write.</li>
|
|
</ul>
|
|
</li>
|
|
<li>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.</li>
|
|
</ul>
|
|
<p>When talking about bit positions, the numbers are <em>zero indexed</em> just like an
|
|
array index is.</p>
|
|
<a class="header" href="#the-display-control-register" id="the-display-control-register"><h1>The Display Control Register</h1></a>
|
|
<p>The display control register is our first actual IO Register. GBATEK gives it the
|
|
shorthand <a href="http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol">DISPCNT</a>, so
|
|
you might see it under that name if you read other guides.</p>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<p>The display control register holds a <code>u16</code> value, and is located at <code>0x0400_0000</code>.</p>
|
|
<p>Many of the bits here won't mean much to you right now. <strong>That is fine.</strong> 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.</p>
|
|
<a class="header" href="#video-modes" id="video-modes"><h2>Video Modes</h2></a>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<p>Modes 3, 4, and 5 are "bitmap" modes. These let you write individual pixels to
|
|
locations on the screen.</p>
|
|
<ul>
|
|
<li><strong>Mode 3</strong> 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 <code>u16</code> value, and the highest bit is
|
|
simply ignored.</li>
|
|
<li><strong>Mode 4</strong> is full resolution paletted color. Instead of being a <code>u16</code> color, each
|
|
pixel value is a <code>u8</code> 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.</li>
|
|
<li><strong>Mode 5</strong> 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.</li>
|
|
</ul>
|
|
<a class="header" href="#cgb-mode" id="cgb-mode"><h2>CGB Mode</h2></a>
|
|
<p>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.</p>
|
|
<p>This bit is on if the CPU is in CGB mode.</p>
|
|
<a class="header" href="#page-flipping" id="page-flipping"><h2>Page Flipping</h2></a>
|
|
<p>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.</p>
|
|
<p>The second page always starts at <code>0x0600_A000</code>.</p>
|
|
<a class="header" href="#oam-vram-and-blanking" id="oam-vram-and-blanking"><h2>OAM, VRAM, and Blanking</h2></a>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<a class="header" href="#screen-layers" id="screen-layers"><h2>Screen Layers</h2></a>
|
|
<p>Bits 8 through 11 control if Background layers 0 through 3 should be active.</p>
|
|
<p>Bit 12 affects the Object layer.</p>
|
|
<p>Note that not all background layers are available in all video modes:</p>
|
|
<ul>
|
|
<li>Mode 0: all</li>
|
|
<li>Mode 1: 0/1/2</li>
|
|
<li>Mode 2: 2/3</li>
|
|
<li>Mode 3/4/5: 2</li>
|
|
</ul>
|
|
<p>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.</p>
|
|
<a class="header" href="#in-conclusion" id="in-conclusion"><h2>In Conclusion...</h2></a>
|
|
<p>So what did we do to the display control register in <code>hello1</code>?</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
(0x04000000 as *mut u16).write_volatile(0x0403);
|
|
#}</code></pre></pre>
|
|
<p>First let's <a href="https://www.wolframalpha.com/input/?i=0x0403">convert that to
|
|
binary</a>, and we get
|
|
<code>0b100_0000_0011</code>. So, that's setting Mode 3 with background 2 enabled and
|
|
nothing else special.</p>
|
|
<a class="header" href="#video-memory-intro" id="video-memory-intro"><h1>Video Memory Intro</h1></a>
|
|
<p>The GBA's Video RAM is 96k stretching from <code>0x0600_0000</code> to <code>0x0601_7FFF</code>.</p>
|
|
<p>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.</p>
|
|
<p>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 <code>5</code> to <code>0x0600_0000</code>, then both <code>0x0600_0000</code> and ALSO
|
|
<code>0x0600_0001</code> will have the byte <code>5</code> 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
|
|
<code>memcopy</code> or <code>memset</code> as well, because they're byte oriented by default and
|
|
don't know to follow the special rules.</p>
|
|
<a class="header" href="#rgb15" id="rgb15"><h2>RGB15</h2></a>
|
|
<p>As I said before, RGB15 stores a color within a <code>u16</code> value using 5 bits for
|
|
each color channel.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const RED: u16 = 0b0_00000_00000_11111;
|
|
pub const GREEN: u16 = 0b0_00000_11111_00000;
|
|
pub const BLUE: u16 = 0b0_11111_00000_00000;
|
|
#}</code></pre></pre>
|
|
<p>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.</p>
|
|
<a class="header" href="#mode-3" id="mode-3"><h2>Mode 3</h2></a>
|
|
<p>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:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
let row_five_col_seven = 5 + (7 * SCREEN_WIDTH);
|
|
#}</code></pre></pre>
|
|
<p>To draw a pixel, we just write a value at the address for the row and col that
|
|
we want to draw to.</p>
|
|
<a class="header" href="#mode-4" id="mode-4"><h2>Mode 4</h2></a>
|
|
<p>Mode 4 introduces page flipping. Instead of one giant page at <code>0x0600_0000</code>,
|
|
there's Page 0 at <code>0x0600_0000</code> and then Page 1 at <code>0x0600_A000</code>. The resolution
|
|
for each page is the same as above, but instead of writing <code>u16</code> values, the
|
|
memory is treated as <code>u8</code> indexes into PALRAM. The PALRAM starts at
|
|
<code>0x0500_0000</code>, and there's enough space for 256 palette entries (each a <code>u16</code>).</p>
|
|
<p>To set the color of a palette entry we just do a normal <code>u16</code> write_volatile.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
(0x0500_0000 as *mut u16).offset(target_index).write_volatile(new_color)
|
|
#}</code></pre></pre>
|
|
<p>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</p>
|
|
<ol>
|
|
<li>Read the full <code>u16</code> it's a part of.</li>
|
|
<li>Clear the half of the <code>u16</code> we're going to replace</li>
|
|
<li>Write the half of the <code>u16</code> we're going to replace with the new value</li>
|
|
<li>Write that result back to the address.</li>
|
|
</ol>
|
|
<p>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 <code>u16</code> at
|
|
half the byte offset rounded down. If the offset is ODD it'll be the low bits of
|
|
the <code>u16</code> at half the byte.</p>
|
|
<p>Does that make sense?</p>
|
|
<ul>
|
|
<li>If we want to write pixel (0,0) the byte offset is 0, so we change the high
|
|
bits of <code>u16</code> offset 0. Then we want to write to (1,0), so the byte offset is
|
|
1, so we change the low bits of <code>u16</code> offset 0. The pixels are next to each
|
|
other, and the target bytes are next to each other, good so far.</li>
|
|
<li>If we want to write to (5,6) that'd be byte <code>5 + 6 * 240 = 1445</code>, so we'd
|
|
target the low bits of <code>u16</code> offset <code>floor(1445/2) = 722</code>.</li>
|
|
</ul>
|
|
<p>As you can see, trying to write individual pixels in Mode 4 is mostly a bad
|
|
time. Fret not! We don't <em>have</em> to write individual bytes. If our data is
|
|
arranged correctly ahead of time we can just write <code>u16</code> or <code>u32</code> values
|
|
directly. The video hardware doesn't care, it'll get along just fine.</p>
|
|
<a class="header" href="#mode-5" id="mode-5"><h2>Mode 5</h2></a>
|
|
<p>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.</p>
|
|
<p>Mode 5 is full <code>u16</code> color, but only 160w x 128h per page.</p>
|
|
<a class="header" href="#in-conclusion-1" id="in-conclusion-1"><h2>In Conclusion...</h2></a>
|
|
<p>So what got written into VRAM in <code>hello1</code>?</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
(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);
|
|
#}</code></pre></pre>
|
|
<p>So at pixels <code>(120,80)</code>, <code>(136,80)</code>, and <code>(120,96)</code> we write three values. Once
|
|
again we probably need to <a href="https://www.wolframalpha.com/">convert them</a> into
|
|
binary to make sense of it.</p>
|
|
<ul>
|
|
<li>0x001F: 0b0_00000_00000_11111</li>
|
|
<li>0x03E0: 0b0_00000_11111_00000</li>
|
|
<li>0x7C00: 0b0_11111_00000_00000</li>
|
|
</ul>
|
|
<p>Ah, of course, a red pixel, a green pixel, and a blue pixel.</p>
|
|
<a class="header" href="#hello2" id="hello2"><h1>hello2</h1></a>
|
|
<p>Okay so let's have a look again:</p>
|
|
<p><code>hello1</code></p>
|
|
<pre><pre class="playpen"><code class="language-rust">#![feature(start)]
|
|
#![no_std]
|
|
|
|
#[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 {}
|
|
}
|
|
}
|
|
</code></pre></pre>
|
|
<p>Now let's clean this up so that it's clearer what's going on.</p>
|
|
<p>First we'll label that display control stuff, including using the <code>VolatilePtr</code>
|
|
type from the volatile explanation:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x04000000 as *mut u16);
|
|
pub const MODE3: u16 = 3;
|
|
pub const BG2: u16 = 0b100_0000_0000;
|
|
#}</code></pre></pre>
|
|
<p>Next we make some const values for the actual pixel drawing</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const VRAM: usize = 0x06000000;
|
|
pub const SCREEN_WIDTH: isize = 240;
|
|
#}</code></pre></pre>
|
|
<p>Note that VRAM has to be interpreted in different ways depending on mode, so we
|
|
just leave it as <code>usize</code> and we'll cast it into the right form closer to the
|
|
actual use.</p>
|
|
<p>Next we want a small helper function for putting together a color value.
|
|
Happily, this one can even be declared as a <code>const</code> 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.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
|
blue << 10 | green << 5 | red
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>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.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
|
|
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>So now we've got this:</p>
|
|
<p><code>hello2</code></p>
|
|
<pre><pre class="playpen"><code class="language-rust">#![feature(start)]
|
|
#![no_std]
|
|
|
|
#[panic_handler]
|
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|
loop {}
|
|
}
|
|
|
|
#[start]
|
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
|
unsafe {
|
|
DISPCNT.write(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 {}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
#[repr(transparent)]
|
|
pub struct VolatilePtr<T>(pub *mut T);
|
|
impl<T> VolatilePtr<T> {
|
|
pub unsafe fn read(&self) -> T {
|
|
core::ptr::read_volatile(self.0)
|
|
}
|
|
pub unsafe fn write(&self, data: T) {
|
|
core::ptr::write_volatile(self.0, data);
|
|
}
|
|
pub unsafe fn offset(self, count: isize) -> Self {
|
|
VolatilePtr(self.0.wrapping_offset(count))
|
|
}
|
|
}
|
|
|
|
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(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) {
|
|
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
|
}
|
|
</code></pre></pre>
|
|
<p>Exact same program that we started with, but much easier to read.</p>
|
|
<p>Of course, in the full <code>gba</code> crate that this book is a part of we have these and
|
|
other elements all labeled and sorted out for you (not identically, but
|
|
similarly). Still, for educational purposes it's often best to do it yourself at
|
|
least once.</p>
|
|
<a class="header" href="#ch-2-user-input" id="ch-2-user-input"><h1>Ch 2: User Input</h1></a>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<p>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 <code>hello2.rs</code> named
|
|
<code>light_cycle.rs</code> 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.</p>
|
|
<a class="header" href="#the-key-input-register" id="the-key-input-register"><h1>The Key Input Register</h1></a>
|
|
<p>The Key Input Register is our next IO register. Its shorthand name is
|
|
<a href="http://problemkaputt.de/gbatek.htm#gbakeypadinput">KEYINPUT</a> and it's a <code>u16</code>
|
|
at <code>0x4000130</code>. The entire register is obviously read only, you can't tell the
|
|
GBA what buttons are pressed.</p>
|
|
<p>Each button is exactly one bit:</p>
|
|
<table><thead><tr><th align="center"> Bit </th><th align="center"> Button </th></tr></thead><tbody>
|
|
<tr><td align="center"> 0 </td><td align="center"> A </td></tr>
|
|
<tr><td align="center"> 1 </td><td align="center"> B </td></tr>
|
|
<tr><td align="center"> 2 </td><td align="center"> Select </td></tr>
|
|
<tr><td align="center"> 3 </td><td align="center"> Start </td></tr>
|
|
<tr><td align="center"> 4 </td><td align="center"> Right </td></tr>
|
|
<tr><td align="center"> 5 </td><td align="center"> Left </td></tr>
|
|
<tr><td align="center"> 6 </td><td align="center"> Up </td></tr>
|
|
<tr><td align="center"> 7 </td><td align="center"> Down </td></tr>
|
|
<tr><td align="center"> 8 </td><td align="center"> R </td></tr>
|
|
<tr><td align="center"> 9 </td><td align="center"> L </td></tr>
|
|
</tbody></table>
|
|
<p>The higher bits above are not used at all.</p>
|
|
<p>Similar to other old hardware devices, the convention here is that a button's
|
|
bit is <strong>clear when pressed, active when released</strong>. In other words, when the
|
|
user is not touching the device at all the KEYINPUT value will read
|
|
<code>0b0000_0011_1111_1111</code>. 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.</p>
|
|
<p>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.</p>
|
|
<p>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 <code>u16</code>) 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.</p>
|
|
<p>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.</p>
|
|
<a class="header" href="#key-input-code" id="key-input-code"><h2>Key Input Code</h2></a>
|
|
<p>Let's get down to some code. First we want to make a way to read the address as
|
|
a <code>u16</code> and then wrap that in our newtype which will implement methods for
|
|
reading and writing the key bits.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const KEYINPUT: VolatilePtr<u16> = VolatilePtr(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 key_input() -> KeyInputSetting {
|
|
unsafe { KeyInputSetting(KEYINPUT.read()) }
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Now we want a way to check if a key is <em>being pressed</em>, 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".</p>
|
|
<p>Normally we'd pick a constant for the bit we want, <code>&</code> it with our value, and
|
|
then check for <code>val != 0</code>. Since the bit we're looking for is <code>0</code> in the "true"
|
|
state we still pick the same constant and we still do the <code>&</code>, but we test with
|
|
<code>== 0</code>. 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.</p>
|
|
<p>All we have to do is ask our good friend
|
|
<a href="https://rust.godbolt.org/z/d-8oCe">Godbolt</a> what's gonna happen when the code
|
|
compiles. The link there has the page set for the <code>stable</code> 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 <code>thumbv6m-none-eabi</code>, 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.</p>
|
|
<p>It turns out that the <code>!=0</code> test is 4 instructions and the <code>==0</code> 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 <code>!=0</code> test and
|
|
then adjust how we initially read the register to compensate. By using xor with
|
|
a mask for only the 10 used bits we can flip the "low when pressed" values so
|
|
that the entire result has active bits in all positions where a key is pressed.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub fn key_input() -> KeyInputSetting {
|
|
unsafe { KeyInputSetting(KEYINPUT.read_volatile() ^ 0b0000_0011_1111_1111) }
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>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 <code>const</code> values and then have a method that takes a
|
|
value and says if that bit is on.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
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
|
|
}
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>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.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
let input_contains_a_and_l = input.contains(KEY_A + KEY_L);
|
|
#}</code></pre></pre>
|
|
<p>And we wanted to save the state of an old frame and compare it to the current
|
|
frame to see what was different:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub fn difference(&self, other: KeyInputSetting) -> KeyInputSetting {
|
|
KeyInputSetting(self.0 ^ other.0)
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Anything that's "in" the difference output is a key that <em>changed</em>, 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:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
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
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>And for the arrow pad, we'll make an enum that easily casts into <code>i32</code>. Whenever
|
|
we're working with stuff we can try to use <code>i32</code> / <code>isize</code> 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 <code>match</code> and be sure that we've covered all our
|
|
cases.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
/// 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,
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Now, how do we determine <em>which way</em> 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.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
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
|
|
}
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>So then in our game, every frame we can check for <code>column_direction</code> and
|
|
<code>row_direction</code> and then apply those to the player's current position to make
|
|
them move around the screen.</p>
|
|
<p>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.</p>
|
|
<a class="header" href="#the-vcount-register" id="the-vcount-register"><h1>The VCount Register</h1></a>
|
|
<p>There's an IO register called
|
|
<a href="http://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus">VCOUNT</a> that shows
|
|
you, what else, the Vertical (row) COUNT(er). It's a <code>u16</code> at address
|
|
<code>0x0400_0006</code>, and it's how we'll be doing our very poor quality vertical sync
|
|
code to start.</p>
|
|
<ul>
|
|
<li><strong>What makes it poor?</strong> 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.</li>
|
|
<li><strong>Why is this bad?</strong> 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.</li>
|
|
<li><strong>Can we do better?</strong> 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.</li>
|
|
</ul>
|
|
<p>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 <em>past</em> 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.</p>
|
|
<ul>
|
|
<li>One pixel is 4 CPU cycles</li>
|
|
<li>HDraw is 240 pixels, HBlank is 68 pixels (1,232 cycles per full scanline)</li>
|
|
<li>VDraw is 150 scanlines, VBlank is 68 scanlines (280,896 cycles per full refresh)</li>
|
|
</ul>
|
|
<p>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.</p>
|
|
<p>So first we want a way to check the vcount value at all:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const VCOUNT: VolatilePtr<u16> = VolatilePtr(0x0400_0006 as *mut u16);
|
|
|
|
pub fn vcount() -> u16 {
|
|
unsafe { VCOUNT.read() }
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Then we want two little helper functions to wait until VBlank and vdraw.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const SCREEN_HEIGHT: isize = 160;
|
|
|
|
pub fn wait_until_vblank() {
|
|
while vcount() < SCREEN_HEIGHT as u16 {}
|
|
}
|
|
|
|
pub fn wait_until_vdraw() {
|
|
while vcount() >= SCREEN_HEIGHT as u16 {}
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>And... that's it. No special types to be made this time around, it's just a
|
|
number we read out of memory.</p>
|
|
<a class="header" href="#light_cycle" id="light_cycle"><h1>light_cycle</h1></a>
|
|
<p>Now let's make a game of "light_cycle" with our new knowledge.</p>
|
|
<a class="header" href="#gameplay" id="gameplay"><h2>Gameplay</h2></a>
|
|
<p><code>light_cycle</code> 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.</p>
|
|
<a class="header" href="#operations" id="operations"><h2>Operations</h2></a>
|
|
<p>We need some better drawing operations this time around.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub unsafe fn mode3_clear_screen(color: u16) {
|
|
let color = color as u32;
|
|
let bulk_color = color << 16 | color;
|
|
let mut ptr = VolatilePtr(VRAM as *mut u32);
|
|
for _ in 0..SCREEN_HEIGHT {
|
|
for _ in 0..(SCREEN_WIDTH / 2) {
|
|
ptr.write(bulk_color);
|
|
ptr = ptr.offset(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub unsafe fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
|
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
|
}
|
|
|
|
pub unsafe fn mode3_read_pixel(col: isize, row: isize) -> u16 {
|
|
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).read()
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>The draw pixel and read pixel are both pretty obvious. What's new is the clear
|
|
screen operation. It changes the <code>u16</code> color into a <code>u32</code> and then packs the
|
|
value in twice. Then we write out <code>u32</code> 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.</p>
|
|
<p>Now we just have to fill in the main function:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">#[start]
|
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
|
unsafe {
|
|
DISPCNT.write(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 = 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();
|
|
}
|
|
}
|
|
</code></pre></pre>
|
|
<p>Oh that's a lot more than before!</p>
|
|
<p>First we set Mode 3 and Background 2, we know about that.</p>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<p>Finally, if the player is in bounds and they haven't crashed, we write their
|
|
color into memory at this position.</p>
|
|
<p>Regardless of how it worked out, we hold here until vdraw starts before going to
|
|
the next loop. That's all there is to it.</p>
|
|
<a class="header" href="#the-gba-crate-doesnt-quite-work-like-this" id="the-gba-crate-doesnt-quite-work-like-this"><h2>The gba crate doesn't quite work like this</h2></a>
|
|
<p>Once again, as with the <code>hello1</code> and <code>hello2</code> examples, the <code>gba</code> crate covers
|
|
much of this same ground as our example here, but in slightly different ways.</p>
|
|
<p>Better organization and abstractions are usually only realized once you've used
|
|
more of the whole thing you're trying to work with. If we want to have a crate
|
|
where the whole thing is well integrated with itself, then the examples would
|
|
also end up having to explain about things we haven't really touched on much
|
|
yet. It becomes a lot harder to teach.</p>
|
|
<p>So, going forward, we will continue to teach concepts and build examples that
|
|
don't directly depend on the <code>gba</code> crate. This allows the crate to freely grow
|
|
without all the past examples becoming a great inertia upon it.</p>
|
|
<a class="header" href="#ch-3-memory-and-objects" id="ch-3-memory-and-objects"><h1>Ch 3: Memory and Objects</h1></a>
|
|
<p>Alright so we can do some basic "movement", but we left a big trail in the video
|
|
memory of everywhere we went. Most of the time that's not what we want at all.
|
|
If we want more hardware support we're going to have to use a new video mode. So
|
|
far we've only used Mode 3, but modes 4 and 5 are basically the same. Instead,
|
|
we'll switch focus to using a tiled graphical mode.</p>
|
|
<p>First we will go over the complete GBA memory mapping. Part of this is the
|
|
memory for tiled graphics, but also things like all those IO registers, where
|
|
our RAM is for scratch space, all that stuff. Even if we can't put all of them
|
|
to use at once, it's helpful to have an idea of what will be available in the
|
|
long run.</p>
|
|
<p>Tiled modes bring us three big new concepts that each have their own complexity:
|
|
tiles, backgrounds, and objects. Backgrounds and objects both use tiles, but the
|
|
background is for creating a very large static space that you can scroll around
|
|
the view within, and the objects are about having a few moving bits that appear
|
|
over the background. Careful use of backgrounds and objects is key to having the
|
|
best looking GBA game, so we won't even be able to cover it all in a single
|
|
chapter.</p>
|
|
<p>And, of course, since most games are pretty boring if they're totally static
|
|
we'll touch on the kinds of RNG implementations you might want to have on a GBA.
|
|
Most general purpose RNGs that you find are rather big compared to the amount of
|
|
memory we want to give them, and they often use a lot of <code>u64</code> operations, so
|
|
they end up much slower on a 32-bit machine like the GBA (you can lower 64-bit
|
|
ops to combinations of 32-bit ops, but that's quite a bit more work). We'll
|
|
cover a few RNG options that size down the RNG to a good size and a good speed
|
|
without trading away too much in terms of quality.</p>
|
|
<p>To top it all off, we'll make a simple "memory game" sort of thing. There's some
|
|
face down cards in a grid, you pick one to check, then you pick the other to
|
|
check, and then if they match the pair disappears.</p>
|
|
<a class="header" href="#drawing-priority" id="drawing-priority"><h2>Drawing Priority</h2></a>
|
|
<p>Both backgrounds and objects can have "priority" values associated with them.
|
|
TONC and GBATEK have <em>opposite</em> ideas of what it means to have the "highest"
|
|
priority. TONC goes by highest numerical value, and GBATEK goes by what's on the
|
|
z-layer closest to the user. Let's list out the rules as clearly as we can:</p>
|
|
<ul>
|
|
<li>Priority is always two bits, so 0 through 3.</li>
|
|
<li>Priority conceptually proceeds in drawing passes that count <em>down</em>, so any
|
|
priority 3 things can get covered up by priority 2 things. In truth there's
|
|
probably depth testing and buffering stuff going on so it's all one single
|
|
pass, but conceptually we will imagine it happening as all of the 3 elements,
|
|
then all of 2, and so on.</li>
|
|
<li>Objects always draw over top of backgrounds of equal priority.</li>
|
|
<li>Within things of the same type and priority, the lower numbered element "wins"
|
|
and gets its pixel drawn (bg0 is favored over bg1, obj0 is favored over obj1,
|
|
etc).</li>
|
|
</ul>
|
|
<a class="header" href="#gba-memory-mapping" id="gba-memory-mapping"><h1>GBA Memory Mapping</h1></a>
|
|
<p>The <a href="http://problemkaputt.de/gbatek.htm#gbamemorymap">GBA Memory Map</a> has
|
|
several memory portions to it, each with their own little differences. Most of
|
|
the memory has pre-determined use according to the hardware, but there is also
|
|
space for games to use as a scratch pad in whatever way the game sees fit.</p>
|
|
<p>The memory ranges listed here are <em>inclusive</em>, so they end with a lot of F's
|
|
and E's.</p>
|
|
<p>We've talked about volatile memory before, but just as a reminder I'll say that
|
|
all of the memory we'll talk about here should be accessed using volatile with
|
|
two exceptions:</p>
|
|
<ol>
|
|
<li>Work RAM (both internal and external) can be used normally, and if the
|
|
compiler is able to totally elide some reads and writes that's okay.</li>
|
|
<li>However, if you set aside any space in Work RAM where an interrupt will
|
|
communicate with the main program then that specific location will have to
|
|
keep using volatile access, since the compiler never knows when an interrupt
|
|
will actually happen.</li>
|
|
</ol>
|
|
<a class="header" href="#bios--system-rom" id="bios--system-rom"><h2>BIOS / System ROM</h2></a>
|
|
<ul>
|
|
<li><code>0x0</code> to <code>0x3FFF</code> (16k)</li>
|
|
</ul>
|
|
<p>This is special memory for the BIOS. It is "read-only", but even then it's only
|
|
accessible when the program counter is pointing into the BIOS region. At all
|
|
other times you get a <a href="http://problemkaputt.de/gbatek.htm#gbaunpredictablethings">garbage
|
|
value</a> back when you
|
|
try to read out of the BIOS.</p>
|
|
<a class="header" href="#external-work-ram--ewram" id="external-work-ram--ewram"><h2>External Work RAM / EWRAM</h2></a>
|
|
<ul>
|
|
<li><code>0x2000000</code> to <code>0x203FFFF</code> (256k)</li>
|
|
</ul>
|
|
<p>This is a big pile of space, the use of which is up to each game. However, the
|
|
external work ram has only a 16-bit bus (if you read/write a 32-bit value it
|
|
silently breaks it up into two 16-bit operations) and also 2 wait cycles (extra
|
|
CPU cycles that you have to expend <em>per 16-bit bus use</em>).</p>
|
|
<p>It's most helpful to think of EWRAM as slower, distant memory, similar to the
|
|
"heap" in a normal application. You can take the time to go store something
|
|
within EWRAM, or to load it out of EWRAM, but if you've got several operations
|
|
to do in a row and you're worried about time you should pull that value into
|
|
local memory, work on your local copy, and then push it back out to EWRAM.</p>
|
|
<a class="header" href="#internal-work-ram--iwram" id="internal-work-ram--iwram"><h2>Internal Work RAM / IWRAM</h2></a>
|
|
<ul>
|
|
<li><code>0x3000000</code> to <code>0x3007FFF</code> (32k)</li>
|
|
</ul>
|
|
<p>This is a smaller pile of space, but it has a 32-bit bus and no wait.</p>
|
|
<p>By default, <code>0x3007F00</code> to <code>0x3007FFF</code> is reserved for interrupt and BIOS use.
|
|
The rest of it is totally up to you. The user's stack space starts at
|
|
<code>0x3007F00</code> and proceeds <em>down</em> from there. For best results you should probably
|
|
start at <code>0x3000000</code> and then go upwards. Under normal use it's unlikely that
|
|
the two memory regions will crash into each other.</p>
|
|
<a class="header" href="#io-registers-1" id="io-registers-1"><h2>IO Registers</h2></a>
|
|
<ul>
|
|
<li><code>0x4000000</code> to <code>0x40003FE</code></li>
|
|
</ul>
|
|
<p>We've touched upon a few of these so far, and we'll get to more later. At the
|
|
moment it is enough to say that, as you might have guessed, all of them live in
|
|
this region. Each individual register is a <code>u16</code> or <code>u32</code> and they control all
|
|
sorts of things. We'll actually be talking about some more of them in this very
|
|
chapter, because that's how we'll control some of the background and object
|
|
stuff.</p>
|
|
<a class="header" href="#palette-ram--palram" id="palette-ram--palram"><h2>Palette RAM / PALRAM</h2></a>
|
|
<ul>
|
|
<li><code>0x5000000</code> to <code>0x50003FF</code> (1k)</li>
|
|
</ul>
|
|
<p>Palette RAM has a 16-bit bus, which isn't really a problem because it
|
|
conceptually just holds <code>u16</code> values. There's no automatic wait state, but if
|
|
you try to access the same location that the display controller is accessing you
|
|
get bumped by 1 cycle. Since the display controller can use the palette ram any
|
|
number of times per scanline it's basically impossible to predict if you'll have
|
|
to do a wait or not during VDraw. During VBlank you won't have any wait of
|
|
course.</p>
|
|
<p>PALRAM is among the memory where there's weirdness if you try to write just one
|
|
byte: if you try to write just 1 byte, it writes that byte into <em>both</em> parts of
|
|
the larger 16-bit location. This doesn't really affect us much with PALRAM,
|
|
because palette values are all supposed to be <code>u16</code> anyway.</p>
|
|
<p>The palette memory actually contains not one, but <em>two</em> sets of palettes. First
|
|
there's 256 entries for the background palette data (starting at <code>0x5000000</code>),
|
|
and then there's 256 entries for object palette data (starting at <code>0x5000200</code>).</p>
|
|
<p>The GBA also has two modes for palette access: 8-bits-per-pixel (8bpp) and
|
|
4-bits-per-pixel (4bpp).</p>
|
|
<ul>
|
|
<li>In 8bpp mode an 8-bit palette index value within a background or sprite
|
|
simply indexes directly into the 256 slots for that type of thing.</li>
|
|
<li>In 4bpp mode a 4-bit palette index value within a background or sprite
|
|
specifies an index within a particular "palbank" (16 palette entries each),
|
|
and then a <em>separate</em> setting outside of the graphical data determines which
|
|
palbank is to be used for that background or object (the screen entry data for
|
|
backgrounds, and the object attributes for objects).</li>
|
|
</ul>
|
|
<a class="header" href="#transparency" id="transparency"><h3>Transparency</h3></a>
|
|
<p>When a pixel within a background or object specifies index 0 as its palette
|
|
entry it is treated as a transparent pixel. This means that in 8bpp mode there's
|
|
only 255 actual color options (0 being transparent), and in 4bpp mode there's
|
|
only 15 actual color options available within each palbank (the 0th entry of
|
|
<em>each</em> palbank is transparent).</p>
|
|
<p>Individual backgrounds, and individual objects, each determine if they're 4bpp
|
|
or 8bpp separately, so a given overall palette slot might map to a used color in
|
|
8bpp and an unused/transparent color in 4bpp. If you're a palette wizard.</p>
|
|
<p>Palette slot 0 of the overall background palette is used to determine the
|
|
"backdrop" color. That's the color you see if no background or object ends up
|
|
being rendered within a given pixel.</p>
|
|
<p>Since display mode 3 and display mode 5 don't use the palette, they cannot
|
|
benefit from transparency.</p>
|
|
<a class="header" href="#video-ram--vram" id="video-ram--vram"><h2>Video RAM / VRAM</h2></a>
|
|
<ul>
|
|
<li><code>0x6000000</code> to <code>0x6017FFF</code> (96k)</li>
|
|
</ul>
|
|
<p>We've used this before! VRAM has a 16-bit bus and no wait. However, the same as
|
|
with PALRAM, the "you might have to wait if the display controller is looking at
|
|
it" rule applies here.</p>
|
|
<p>Unfortunately there's not much more exact detail that can be given about VRAM.
|
|
The use of the memory depends on the video mode that you're using.</p>
|
|
<p>One general detail of note is that you can't write individual bytes to any part
|
|
of VRAM. Depending on mode and location, you'll either get your bytes doubled
|
|
into both the upper and lower parts of the 16-bit location targeted, or you
|
|
won't even affect the memory. This usually isn't a big deal, except in two
|
|
situations:</p>
|
|
<ul>
|
|
<li>In Mode 4, if you want to change just 1 pixel, you'll have to be very careful
|
|
to read the old <code>u16</code>, overwrite just the byte you wanted to change, and then
|
|
write that back.</li>
|
|
<li>In any display mode, avoid using <code>memcopy</code> to place things into VRAM.
|
|
It's written to be byte oriented, and only does 32-bit transfers under select
|
|
conditions. The rest of the time it'll copy one byte at a time and you'll get
|
|
either garbage or nothing at all.</li>
|
|
</ul>
|
|
<a class="header" href="#object-attribute-memory--oam" id="object-attribute-memory--oam"><h2>Object Attribute Memory / OAM</h2></a>
|
|
<ul>
|
|
<li><code>0x7000000</code> to <code>0x70003FF</code> (1k)</li>
|
|
</ul>
|
|
<p>The Object Attribute Memory has a 32-bit bus and no default wait, but suffers
|
|
from the "you might have to wait if the display controller is looking at it"
|
|
rule. You cannot write individual bytes to OAM at all, but that's not really a
|
|
problem because all the fields of the data types within OAM are either <code>i16</code> or
|
|
<code>u16</code> anyway.</p>
|
|
<p>Object attribute memory is the wildest yet: it conceptually contains two types
|
|
of things, but they're <em>interlaced</em> with each other all the way through.</p>
|
|
<p>Now, <a href="http://problemkaputt.de/gbatek.htm#lcdobjoamattributes">GBATEK</a> and
|
|
<a href="https://www.cs.rit.edu/%7Etjh8300/CowBite/CowBiteSpec.htm#OAM%20(sprites)">CowByte</a>
|
|
doesn't quite give names to the two data types here.
|
|
<a href="https://www.coranac.com/tonc/text/regobj.htm#sec-oam">TONC</a> calls them
|
|
<code>OBJ_ATTR</code> and <code>OBJ_AFFINE</code>, but we'll be giving them names fitting with the
|
|
Rust naming convention. Just know that if you try to talk about it with others
|
|
they might not be using the same names. In Rust terms their layout would look
|
|
like this:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[repr(C)]
|
|
pub struct ObjectAttributes {
|
|
attr0: u16,
|
|
attr1: u16,
|
|
attr2: u16,
|
|
filler: i16,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct AffineMatrix {
|
|
filler0: [u16; 3],
|
|
pa: i16,
|
|
filler1: [u16; 3],
|
|
pb: i16,
|
|
filler2: [u16; 3],
|
|
pc: i16,
|
|
filler3: [u16; 3],
|
|
pd: i16,
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>(Note: the <code>#[repr(C)]</code> part just means that Rust must lay out the data exactly
|
|
in the order we specify, which otherwise it is not required to do).</p>
|
|
<p>So, we've got 1024 bytes in OAM and each <code>ObjectAttributes</code> value is 8 bytes, so
|
|
naturally we can support up to 128 objects.</p>
|
|
<p><em>At the same time</em>, we've got 1024 bytes in OAM and each <code>AffineMatrix</code> is 32
|
|
bytes, so we can have 32 of them.</p>
|
|
<p>But, as I said, these things are all <em>interlaced</em> with each other. See how
|
|
there's "filler" fields in each struct? If we imagine the OAM as being just an
|
|
array of one type or the other, indexes 0/1/2/3 of the <code>ObjectAttributes</code> array
|
|
would line up with index 0 of the <code>AffineMatrix</code> array. It's kinda weird, but
|
|
that's just how it works. When we setup functions to read and write these values
|
|
we'll have to be careful with how we do it. We probably <em>won't</em> want to use
|
|
those representations above, at least not with the <code>AffineMatrix</code> type, because
|
|
they're quite wasteful if you want to store just object attributes or just
|
|
affine matrices.</p>
|
|
<a class="header" href="#game-pak-rom--flash-rom" id="game-pak-rom--flash-rom"><h2>Game Pak ROM / Flash ROM</h2></a>
|
|
<ul>
|
|
<li><code>0x8000000</code> to <code>0x9FFFFFF</code> (wait 0)</li>
|
|
<li><code>0xA000000</code> to <code>0xBFFFFFF</code> (wait 1)</li>
|
|
<li><code>0xC000000</code> to <code>0xDFFFFFF</code> (wait 2)</li>
|
|
<li>Max of 32Mb</li>
|
|
</ul>
|
|
<p>These portions of the memory are less fixed, because they depend on the precise
|
|
details of the game pak you've inserted into the GBA. In general, they connect
|
|
to the game pak ROM and/or Flash memory, using a 16-bit bus. The ROM is
|
|
read-only, but the Flash memory (if any) allows writes.</p>
|
|
<p>The game pak ROM is listed as being in three sections, but it's actually the
|
|
same memory being effectively mirrored into three different locations. The
|
|
mirror that you choose to access the game pak through affects which wait state
|
|
setting it uses (configured via IO register of course). Unfortunately, the
|
|
details come down more to the game pak hardware that you load your game onto
|
|
than anything else, so there's not much I can say right here. We'll eventually
|
|
talk about it more later when I'm forced to do the boring thing and just cover
|
|
all the IO registers that aren't covered anywhere else.</p>
|
|
<p>One thing of note is the way that the 16-bit bus affects us: the instructions to
|
|
execute are coming through the same bus as the rest of the game data, so we want
|
|
them to be as compact as possible. The ARM chip in the GBA supports two
|
|
different instruction sets, "thumb" and "non-thumb". The thumb mode instructions
|
|
are 16-bit, so they can each be loaded one at a time, and the non-thumb
|
|
instructions are 32-bit, so we're at a penalty if we execute them directly out
|
|
of the game pak. However, some things will demand that we use non-thumb code, so
|
|
we'll have to deal with that eventually. It's possible to switch between modes,
|
|
but it's a pain to keep track of what mode you're in because there's not
|
|
currently support for it in Rust itself (perhaps some day). So we'll stick with
|
|
thumb code as much as we possibly can, that's why our target profile for our
|
|
builds starts with <code>thumbv4</code>.</p>
|
|
<a class="header" href="#game-pak-sram" id="game-pak-sram"><h2>Game Pak SRAM</h2></a>
|
|
<ul>
|
|
<li><code>0xE000000</code> to <code>0xE00FFFF</code> (64k)</li>
|
|
</ul>
|
|
<p>The game pak SRAM has an 8-bit bus. Why did Pokémon always take so long to save?
|
|
Saving the whole game one byte at a time is why. The SRAM also has some amount
|
|
of wait, but as with the ROM, the details depend on your game pak hardware (and
|
|
also as with ROM, you can adjust the settings with an IO register, should you
|
|
need to).</p>
|
|
<p>One thing to note about the SRAM is that the GBA has a Direct Memory Access
|
|
(DMA) feature that can be used for bulk memory movements in some cases, but the
|
|
DMA <em>cannot</em> access the SRAM region. You really are stuck reading and writing
|
|
one byte at a time when you're using the SRAM.</p>
|
|
<a class="header" href="#tile-data" id="tile-data"><h1>Tile Data</h1></a>
|
|
<p>When using the GBA's hardware graphics, if you want to let the hardware do most
|
|
of the work you have to use Modes 0, 1 or 2. However, to do that we first have
|
|
to learn about how tile data works inside of the GBA.</p>
|
|
<a class="header" href="#tiles" id="tiles"><h2>Tiles</h2></a>
|
|
<p>Fundamentally, a tile is an 8x8 image. If you want anything bigger than 8x8 you
|
|
need to arrange several tiles so that it looks like whatever you're trying to
|
|
draw.</p>
|
|
<p>As was already mentioned, the GBA supports two different color modes: 4 bits per
|
|
pixel and 8 bits per pixel. This means that we have two types of tile that we
|
|
need to model. The pixel bits always represent an index into the PALRAM.</p>
|
|
<ul>
|
|
<li>With 4 bits per pixel, the PALRAM is imagined to be 16 <strong>palbank</strong> sections of
|
|
16 palette entries each. The image data selects the index within the palbank,
|
|
and an external configuration selects which palbank is used.</li>
|
|
<li>With 8 bits per pixel, the PALRAM is imagined to be a single 256 entry array
|
|
and the index just directly picks which of the 256 colors is used.</li>
|
|
</ul>
|
|
<p>Knowing this, we can write the following definitions:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
#[repr(transparent)]
|
|
pub struct Tile4bpp {
|
|
data: [u32; 8]
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
#[repr(transparent)]
|
|
pub struct Tile8bpp {
|
|
data: [u32; 16]
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>I hope this makes sense so far. At 4bpp, we have 4 bits per pixel, times 8
|
|
pixels per line, times 8 lines: 256 bits required. Similarly, at 8 bits per
|
|
pixel we'll need 512 bits. Why are we defining them as arrays of <code>u32</code> values?
|
|
Because when it comes time to do bulk copies the fastest way to it will be to go
|
|
one whole machine word at a time. If we make the data inside the type be an
|
|
array of <code>u32</code> then it'll already be aligned for fast <code>u32</code> bulk copies.</p>
|
|
<p>Keeping track of the current color depth is naturally the <em>programmer's</em>
|
|
problem. If you get it wrong you'll see a whole ton of garbage pixels all over
|
|
the screen, and you'll probably be able to guess why. You know, unless you did
|
|
one of the other things that can make a bunch of garbage pixels show up all over
|
|
the screen. Graphics programming is fun like that.</p>
|
|
<a class="header" href="#charblocks" id="charblocks"><h2>Charblocks</h2></a>
|
|
<p>Tiles don't just sit on their own, they get grouped into <strong>charblocks</strong>. Long
|
|
ago in the distant past, video games were built with hardware that was also used
|
|
to make text terminals. So tile image data was called "character data". In fact
|
|
some guides will even call the regular mode for the background layers "text
|
|
mode", despite the fact that you obviously don't have to show text at all.</p>
|
|
<p>A charblock is 16kb long (<code>0x4000</code> bytes), which means that the number of tiles
|
|
that fit into a charblock depends on your color depth. With 4bpp you get 512
|
|
tiles, and with 8bpp there's 256 tiles. So they'd be something like this:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[derive(Clone, Copy)]
|
|
#[repr(transparent)]
|
|
pub struct Charblock4bpp {
|
|
data: [Tile4bpp; 512],
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[repr(transparent)]
|
|
pub struct Charblock8bpp {
|
|
data: [Tile8bpp; 256],
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>You'll note that we can't even derive <code>Debug</code> or <code>Default</code> any more because the
|
|
arrays are so big. Rust supports Clone and Copy for arrays of any size, but the
|
|
rest is still size 32 or less. We won't generally be making up an entire
|
|
Charblock on the fly though, so it's not a big deal. If we <em>absolutely</em> had to,
|
|
we could call <code>core::mem::zeroed()</code>, but we really don't want to be trying to
|
|
build a whole charblock at runtime. We'll usually want to define our tile data
|
|
as <code>const</code> charblock values (or even parts of charblock values) that we then
|
|
load out of the game pak ROM at runtime.</p>
|
|
<p>Anyway, with 16k per charblock and only 96k total in VRAM, it's easy math to see
|
|
that there's 6 different charblocks in VRAM when in a tiled mode. The first four
|
|
of these are for backgrounds, and the other two are for objects. There's rules
|
|
for how a tile ID on a background or object selects a tile within a charblock,
|
|
but since they're different between backgrounds and objects we'll cover that on
|
|
their own pages.</p>
|
|
<a class="header" href="#image-editing" id="image-editing"><h2>Image Editing</h2></a>
|
|
<p>It's very important to note that if you use a normal image editor you'll get
|
|
very bad results if you translate that directly into GBA memory.</p>
|
|
<p>Imagine you have part of an image that's 16 by 16 pixels, aka 2 tiles by 2
|
|
tiles. The data for that bitmap is the 1st row of the 1st tile, then the 1st row
|
|
of the 2nd tile. However, when we translate that into the GBA, the first 8
|
|
pixels will indeed be the first 8 tile pixels, but then the next 8 pixels in
|
|
memory will be used as the <em>2nd row of the first tile</em>, not the 1st row of the
|
|
2nd tile.</p>
|
|
<p>So, how do we fix this?</p>
|
|
<p>Well, the simple but annoying way is to edit your tile image as being an 8 pixel
|
|
wide image and then have the image get super tall as you add more and more
|
|
tiles. It can work, but it's really impractical if you have any multi-tile
|
|
things that you're trying to do.</p>
|
|
<p>Instead, there are some image conversion tools that devkitpro provides in their
|
|
gba-dev section. They let you take normal images and then repackage them and
|
|
export it in various formats that you can then compile into your project.</p>
|
|
<p>Ketsuban uses the <a href="http://www.coranac.com/projects/grit/">grit</a> tool, with the
|
|
following suggestions:</p>
|
|
<ol>
|
|
<li>Include an actual resource file and a file describing it somewhere in your
|
|
project (see <a href="http://www.coranac.com/man/grit/html/index.htm">the grit
|
|
manual</a> for all details
|
|
involved here).</li>
|
|
<li>In a <code>build.rs</code> you run <code>grit</code> on each resource+description pair, such as in
|
|
this <a href="https://gist.github.com/ketsuban/526fa55fbef0a3ccd4c7cd6204f29f94">old gist
|
|
example</a></li>
|
|
<li>Then within your rust code you use the
|
|
<a href="https://doc.rust-lang.org/core/macro.include_bytes.html">include_bytes!</a>
|
|
macro to have the formatted resource be available as a const value you can
|
|
load at runtime.</li>
|
|
</ol>
|
|
<a class="header" href="#regular-backgrounds" id="regular-backgrounds"><h1>Regular Backgrounds</h1></a>
|
|
<p>So, backgrounds, they're cool. Why do we call the ones here "regular"
|
|
backgrounds? Because there's also "affine" backgrounds. However, affine math
|
|
stuff adds a complication, so for now we'll just work with regular backgrounds.
|
|
The non-affine backgrounds are sometimes called "text mode" backgrounds by other
|
|
guides.</p>
|
|
<p>To get your background image working you generally need to perform all of the
|
|
following steps, though I suppose the exact ordering is up to you.</p>
|
|
<a class="header" href="#tiled-video-modes" id="tiled-video-modes"><h2>Tiled Video Modes</h2></a>
|
|
<p>When you want regular tiled display, you must use video mode 0 or 1.</p>
|
|
<ul>
|
|
<li>Mode 0 allows for using all four BG layers (0 through 3) as regular
|
|
backgrounds.</li>
|
|
<li>Mode 1 allows for using BG0 and BG1 as regular backgrounds, BG2 as an affine
|
|
background, and BG3 not at all.</li>
|
|
<li>Mode 2 allows for BG2 and BG3 to be used as affine backgrounds, while BG0 and
|
|
BG1 cannot be used at all.</li>
|
|
</ul>
|
|
<p>We will not cover affine backgrounds in this chapter, so we will naturally be
|
|
using video mode 0.</p>
|
|
<p>Also, note that you have to enable each background layer that you want to use
|
|
within the display control register.</p>
|
|
<a class="header" href="#get-your-palette-ready" id="get-your-palette-ready"><h2>Get Your Palette Ready</h2></a>
|
|
<p>Background palette starts at <code>0x5000000</code> and is 256 <code>u16</code> values long. It'd
|
|
potentially be possible declare a static array starting at a fixed address and
|
|
use a linker script to make sure that it ends up at the right spot in the final
|
|
program, but since we have to use volatile reads and writes with PALRAM anyway,
|
|
we'll just reuse our <code>VolatilePtr</code> type. Something like this:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const PALRAM_BG_BASE: VolatilePtr<u16> = VolatilePtr(0x500_0000 as *mut u16);
|
|
|
|
pub fn bg_palette(slot: usize) -> u16 {
|
|
assert!(slot < 256);
|
|
unsafe { PALRAM_BG_BASE.offset(slot as isize).read() }
|
|
}
|
|
|
|
pub fn set_bg_palette(slot: usize, color: u16) {
|
|
assert!(slot < 256);
|
|
unsafe { PALRAM_BG_BASE.offset(slot as isize).write(color) }
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>As we discussed with the tile color depths, the palette can be utilized as a
|
|
single block of palette values (<code>[u16; 256]</code>) or as 16 palbanks of 16 palette
|
|
values each (<code>[[u16;16]; 16]</code>). This setting is assigned per background layer
|
|
via IO register.</p>
|
|
<a class="header" href="#get-your-tiles-ready" id="get-your-tiles-ready"><h2>Get Your Tiles Ready</h2></a>
|
|
<p>Tile data is placed into charblocks. A charblock is always 16kb, so depending on
|
|
color depth it will have either 256 or 512 tiles within that charblock.
|
|
Charblocks 0, 1, 2, and 3 are all for background tiles. That's a maximum of 2048
|
|
tiles for backgrounds, but as you'll see in a moment a particular tilemap entry
|
|
can't even index that high. Instead, each background layer is assigned a
|
|
"character base block", and then tilemap entries index relative to the character
|
|
base block of that background layer.</p>
|
|
<p>Now, if you want to move in a lot of tile data you'll probably want to use a DMA
|
|
routine, or at least write a function like memcopy32 for fast <code>u32</code> copying from
|
|
ROM into VRAM. However, for now, and because we're being very explicit since
|
|
this is our first time doing it, we'll write it as functions for individual tile
|
|
reads and writes.</p>
|
|
<p>The math works like indexing a pointer, except that we have two sizes we need to
|
|
go by. First you take the base address for VRAM (<code>0x600_0000</code>), then add the
|
|
size of a charblock (16kb) times the charblock you want to place the tile
|
|
within, and then you add the index of the tile slot you're placing it into times
|
|
the size of that type of tile. Like this:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub fn bg_tile_4pp(base_block: usize, tile_index: usize) -> Tile4bpp {
|
|
assert!(base_block < 4);
|
|
assert!(tile_index < 512);
|
|
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
|
unsafe { VolatilePtr(address as *mut Tile4bpp).read() }
|
|
}
|
|
|
|
pub fn set_bg_tile_4pp(base_block: usize, tile_index: usize, tile: Tile4bpp) {
|
|
assert!(base_block < 4);
|
|
assert!(tile_index < 512);
|
|
let address = VRAM + size_of::<Charblock4bpp>() * base_block + size_of::<Tile4bpp>() * tile_index;
|
|
unsafe { VolatilePtr(address as *mut Tile4bpp).write(tile) }
|
|
}
|
|
|
|
pub fn bg_tile_8pp(base_block: usize, tile_index: usize) -> Tile8bpp {
|
|
assert!(base_block < 4);
|
|
assert!(tile_index < 256);
|
|
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
|
unsafe { VolatilePtr(address as *mut Tile8bpp).read() }
|
|
}
|
|
|
|
pub fn set_bg_tile_8pp(base_block: usize, tile_index: usize, tile: Tile8bpp) {
|
|
assert!(base_block < 4);
|
|
assert!(tile_index < 256);
|
|
let address = VRAM + size_of::<Charblock8bpp>() * base_block + size_of::<Tile8bpp>() * tile_index;
|
|
unsafe { VolatilePtr(address as *mut Tile8bpp).write(tile) }
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>For bulk operations, you'd do the exact same math to get your base destination
|
|
pointer, and then you'd get the base source pointer for the tile you're copying
|
|
out of ROM, and then you'd do the bulk copy for the correct number of <code>u32</code>
|
|
values that you're trying to move (8 per tile moved for 4bpp, or 16 per tile
|
|
moved for 8bpp).</p>
|
|
<p><strong>GBA Limitation Note:</strong> on a modern PC (eg: <code>x86</code> or <code>x86_64</code>) you're probably
|
|
used to index based loops and iterator based loops being the same speed. The CPU
|
|
has the ability to do a "fused multiply add", so the base address of the array
|
|
plus desired index * size per element is a single CPU operation to compute. It's
|
|
slightly more complicated if there's arrays within arrays like there are here,
|
|
but with normal arrays it's basically the same speed to index per loop cycle as
|
|
it is to take a base address and then add +1 offset per loop cycle. However, the
|
|
GBA's CPU <em>can't do any of that</em>. On the GBA, there's a genuine speed difference
|
|
between looping over indexes and then indexing each loop (slow) compared to
|
|
using an iterator that just stores an internal pointer and does +1 offset per
|
|
loop until it reaches the end (fast). The repeated indexing itself can by itself
|
|
be an expensive step. If it's like a 3 element array it's no big deal, but if
|
|
you've got a big slice of data to process, be sure to go over it with <code>.iter()</code>
|
|
and <code>.iter_mut()</code> if you can, instead of looping by index. This is Rust and all,
|
|
so probably you were gonna do that anyway, but just a heads up.</p>
|
|
<a class="header" href="#get-your-tilemap-ready" id="get-your-tilemap-ready"><h2>Get your Tilemap ready</h2></a>
|
|
<p>I believe that at one point I alluded to a tilemap existing. Well, just as the
|
|
tiles are arranged into charblocks, the data describing what tile to show in
|
|
what location is arranged into a thing called a <strong>screenblock</strong>.</p>
|
|
<p>A screenblock is placed into VRAM the same as the tile data charblocks. Starting
|
|
at the base of VRAM (<code>0x600_0000</code>) there are 32 slots for the screenblock array.
|
|
Each screenblock is 2048 bytes (<code>0x800</code>). Naturally, if our tiles are using up
|
|
charblock space within VRAM and our tilemaps are using up screenblock space
|
|
within the same VRAM... well it would just be a <em>disaster</em> if they ran in to
|
|
each other. Once again, it's up to you as the programmer to determine how much
|
|
space you want to devote to each thing. Each complete charblock uses up 8
|
|
screenblocks worth of space, but you don't have to fill a complete charblock
|
|
with tiles, so you can be very fiddly with how you split the memory.</p>
|
|
<p>Each screenblock is composed of a series of <em>screenblock entry</em> values, which
|
|
describe what tile index to use and if the tile should be flipped and what
|
|
palbank it should use (if any). Because both regular backgrounds and affine
|
|
backgrounds are composed of screenblocks with entries, and because the affine
|
|
background has a smaller format for screenblock entries, we'll name
|
|
appropriately.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[derive(Clone, Copy)]
|
|
#[repr(transparent)]
|
|
pub struct RegularScreenblock {
|
|
data: [RegularScreenblockEntry; 32 * 32],
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
#[repr(transparent)]
|
|
pub struct RegularScreenblockEntry(u16);
|
|
#}</code></pre></pre>
|
|
<p>So, with one entry per tile, a single screenblock allows for 32x32 tiles worth of
|
|
background.</p>
|
|
<p>The format of a regular screenblock entry is quite simple compared to some of
|
|
the IO register stuff:</p>
|
|
<ul>
|
|
<li>10 bits for tile index (base off of the character base block of the background)</li>
|
|
<li>1 bit for horizontal flip</li>
|
|
<li>1 bit for vertical flip</li>
|
|
<li>4 bits for picking which palbank to use (if 4bpp, otherwise it's ignored)</li>
|
|
</ul>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
impl RegularScreenblockEntry {
|
|
pub fn tile_id(self) -> u16 {
|
|
self.0 & 0b11_1111_1111
|
|
}
|
|
pub fn set_tile_id(&mut self, id: u16) {
|
|
self.0 &= !0b11_1111_1111;
|
|
self.0 |= id;
|
|
}
|
|
pub fn horizontal_flip(self) -> bool {
|
|
(self.0 & (1 << 0xA)) > 0
|
|
}
|
|
pub fn set_horizontal_flip(&mut self, bit: bool) {
|
|
if bit {
|
|
self.0 |= 1 << 0xA;
|
|
} else {
|
|
self.0 &= !(1 << 0xA);
|
|
}
|
|
}
|
|
pub fn vertical_flip(self) -> bool {
|
|
(self.0 & (1 << 0xB)) > 0
|
|
}
|
|
pub fn set_vertical_flip(&mut self, bit: bool) {
|
|
if bit {
|
|
self.0 |= 1 << 0xB;
|
|
} else {
|
|
self.0 &= !(1 << 0xB);
|
|
}
|
|
}
|
|
pub fn palbank_index(self) -> u16 {
|
|
self.0 >> 12
|
|
}
|
|
pub fn set_palbank_index(&mut self, palbank_index: u16) {
|
|
self.0 &= 0b1111_1111_1111;
|
|
self.0 |= palbank_index;
|
|
}
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Now, at either 256 or 512 tiles per charblock, you might be thinking that with a
|
|
10 bit index you can index past the end of one charblock and into the next.
|
|
You'd be right, mostly.</p>
|
|
<p>As long as you stay within the background memory region for charblocks (that is,
|
|
0 through 3), then it all works out. However, if you try to get the background
|
|
rendering to reach outside of the background charblocks you'll get an
|
|
implementation defined result. It's not the dreaded "undefined behavior" we're
|
|
often worried about in programming, but the results <em>are</em> determined by what
|
|
you're running the game on. With GBA hardware you get a bizarre result
|
|
(basically another way to put garbage on the screen). With a DS it acts as if
|
|
the tiles were all 0s. If you use an emulator it might or might not allow for
|
|
you to do this, it's up to the emulator writers.</p>
|
|
<a class="header" href="#set-your-io-registers" id="set-your-io-registers"><h2>Set Your IO Registers</h2></a>
|
|
<p>Instead of being just a single IO register to learn about this time, there's two
|
|
separate groups of related registers.</p>
|
|
<a class="header" href="#background-control" id="background-control"><h3>Background Control</h3></a>
|
|
<ul>
|
|
<li>BG0CNT (<code>0x400_0008</code>): BG0 Control</li>
|
|
<li>BG1CNT (<code>0x400_000A</code>): BG1 Control</li>
|
|
<li>BG2CNT (<code>0x400_000C</code>): BG2 Control</li>
|
|
<li>BG3CNT (<code>0x400_000E</code>): BG3 Control</li>
|
|
</ul>
|
|
<p>Each of these are a read/write <code>u16</code> location. This is where we get to all of
|
|
the important details that we've been putting off.</p>
|
|
<ul>
|
|
<li>2 bits for the priority.</li>
|
|
<li>2 bits for "character base block", the charblock that all of the tile indexes
|
|
for this background are offset from.</li>
|
|
<li>1 bit for mosaic effect being enabled (we'll get to that below).</li>
|
|
<li>1 bit to enable 8bpp, otherwise 4bpp is used.</li>
|
|
<li>5 bits to pick the "screen base block", the screen block that serves as the
|
|
<em>base</em> value for this background.</li>
|
|
<li>1 bit that is <em>not</em> used in regular mode, but in affine mode it can be enabled
|
|
to cause the affine background to wrap around at the edges.</li>
|
|
<li>2 bits for the background size.</li>
|
|
</ul>
|
|
<p>The size works a little funny. When size is 0 only the base screen block is
|
|
used. If size is 1 or 2 then the base screenblock and the following screenblock
|
|
are placed next to each other (horizontally for 1, vertically for 2). If the
|
|
size is 3 then the base screenblock and the following three screenblocks are
|
|
arranged into a 2x2 grid of screenblocks.</p>
|
|
<a class="header" href="#background-offset" id="background-offset"><h3>Background Offset</h3></a>
|
|
<ul>
|
|
<li>BG0HOFS (<code>0x400_0010</code>): BG0 X-Offset</li>
|
|
<li>BG0VOFS (<code>0x400_0012</code>): BG0 Y-Offset</li>
|
|
<li>BG1HOFS (<code>0x400_0014</code>): BG1 X-Offset</li>
|
|
<li>BG1VOFS (<code>0x400_0016</code>): BG1 Y-Offset</li>
|
|
<li>BG2HOFS (<code>0x400_0018</code>): BG2 X-Offset</li>
|
|
<li>BG2VOFS (<code>0x400_001A</code>): BG2 Y-Offset</li>
|
|
<li>BG3HOFS (<code>0x400_001C</code>): BG3 X-Offset</li>
|
|
<li>BG3VOFS (<code>0x400_001E</code>): BG3 Y-Offset</li>
|
|
</ul>
|
|
<p>Each of these are a <em>write only</em> <code>u16</code> location. Bits 0 through 8 are used, so
|
|
the offsets can be 0 through 511. They also only apply in regular backgrounds.
|
|
If a background is in an affine state then you'll use different IO registers to
|
|
control it (discussed in a later chapter).</p>
|
|
<p>The offset that you assign determines the pixel offset of the display area
|
|
relative to the start of the background scene, as if the screen was a camera
|
|
looking at the scene. In other words, as a BG X offset value increases, you can
|
|
think of it as the camera moving to the right, or as that background moving to
|
|
the left. Like when mario walks toward the goal. Similarly, when a BG Y offset
|
|
increases the camera is moving down, or the background is moving up, like when
|
|
mario falls down from a high platform.</p>
|
|
<p>Depending on how much the background is scrolled and the size of the background,
|
|
it will loop.</p>
|
|
<a class="header" href="#mosaic" id="mosaic"><h2>Mosaic</h2></a>
|
|
<p>As a special effect, you can apply mosaic to backgrounds and objects. It's just
|
|
a single flag for each background, so all backgrounds will use the same mosaic
|
|
settings when they have it enabled. What it actually does is split the normal
|
|
image into "blocks" and then each block gets the color of the top left pixel of
|
|
that block. This is the effect you see when link hits an electric foe with his
|
|
sword and the whole screen "buzzes" at you.</p>
|
|
<p>The mosaic control is a <em>write only</em> <code>u16</code> IO register at <code>0x400_004C</code>.</p>
|
|
<p>There's 4 bits each for:</p>
|
|
<ul>
|
|
<li>Horizontal BG stretch</li>
|
|
<li>Vertical BG stretch</li>
|
|
<li>Horizontal object stretch</li>
|
|
<li>Vertical object stretch</li>
|
|
</ul>
|
|
<p>The inputs should be 1 <em>less</em> than the desired block size. So if you set a
|
|
stretch value of 5 then pixels 0-5 would be part of the first block (6 pixels),
|
|
then 6-11 is the next block (another 6 pixels) and so on.</p>
|
|
<p>If you need to make a pixel other than the top left part of each block the one
|
|
that determines the mosaic color you can carefully offset the background or
|
|
image by a tiny bit, but of course that makes every mosaic block change its
|
|
target pixel. You can't change the target pixel on a block by block basis.</p>
|
|
<a class="header" href="#regular-objects" id="regular-objects"><h1>Regular Objects</h1></a>
|
|
<p>As with backgrounds, objects can be used in both an affine and non-affine way.
|
|
For this section we'll focus on the non-affine elements, and then we'll do all
|
|
the affine stuff in a later chapter.</p>
|
|
<a class="header" href="#objects-vs-sprites" id="objects-vs-sprites"><h2>Objects vs Sprites</h2></a>
|
|
<p>As <a href="https://www.coranac.com/tonc/text/regobj.htm">TONC</a> helpfully reminds us
|
|
(and then proceeds to not follow its own advice), we should always try to think
|
|
in terms of <em>objects</em>, not <em>sprites</em>. A sprite is a logical / software concern,
|
|
perhaps a player concern, whereas an object is a hardware concern.</p>
|
|
<p>What's more, a given sprite that the player sees might need more than one object
|
|
to display. Objects must be either square or rectangular (so sprite bits that
|
|
stick out probably call for a second object), and can only be from 8x8 to 64x64
|
|
(so anything bigger has to be two objects lined up to appear as one).</p>
|
|
<a class="header" href="#general-object-info" id="general-object-info"><h2>General Object Info</h2></a>
|
|
<p>Unlike with backgrounds, you can enable the object layer in any video mode.
|
|
There's space for 128 object definitions in OAM.</p>
|
|
<p>The display gets a number of cycles per scanline to process objects: 1210 by
|
|
default, but only 954 if you enable the "HBlank interval free" setting in the
|
|
display control register. The <a href="http://problemkaputt.de/gbatek.htm#lcdobjoverview">cycle cost per
|
|
object</a> depends on the
|
|
object's size and if it's using affine or regular mode, so enabling the HBlank
|
|
interval free setting doesn't cut the number of objects displayable by an exact
|
|
number of objects. The objects are processed in order of their definitions and
|
|
if you run out of cycles then the rest just don't get shown. If there's a
|
|
concern that you might run out of cycles you can place important objects (such
|
|
as the player) at the start of the list and then less important animation
|
|
objects later on.</p>
|
|
<a class="header" href="#ready-the-palette" id="ready-the-palette"><h2>Ready the Palette</h2></a>
|
|
<p>Objects use the palette the same as the background does. The only difference is
|
|
that the palette data for objects starts at <code>0x500_0200</code>.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const PALRAM_OBJECT_BASE: VolatilePtr<u16> = VolatilePtr(0x500_0200 as *mut u16);
|
|
|
|
pub fn object_palette(slot: usize) -> u16 {
|
|
assert!(slot < 256);
|
|
unsafe { PALRAM_OBJECT_BASE.offset(slot as isize).read() }
|
|
}
|
|
|
|
pub fn set_object_palette(slot: usize, color: u16) {
|
|
assert!(slot < 256);
|
|
unsafe { PALRAM_OBJECT_BASE.offset(slot as isize).write(color) }
|
|
}
|
|
#}</code></pre></pre>
|
|
<a class="header" href="#ready-the-tiles" id="ready-the-tiles"><h2>Ready the Tiles</h2></a>
|
|
<p>Objects, as with backgrounds, are composed of 8x8 tiles, and if you want
|
|
something bigger than 8x8 you have to use more than one tile put together.
|
|
Object tiles go into the final two charblocks of VRAM (indexes 4 and 5). Because
|
|
there's only two of them, they are sometimes called the lower block
|
|
(<code>0x601_0000</code>) and the higher/upper block (<code>0x601_4000</code>).</p>
|
|
<p>Tile indexes for sprites always offset from the base of the lower block, and
|
|
they always go 32 bytes at a time, regardless of if the object is set for 4bpp
|
|
or 8bpp. From this we can determine that there's 512 tile slots in each of the
|
|
two object charblocks. However, in video modes 3, 4, and 5 the space for the
|
|
background cuts into the lower charblock, so you can only safely use the upper
|
|
charblock.</p>
|
|
<p>With backgrounds you picked every single tile individually with a bunch of
|
|
screen entry values. Objects don't do that at all. Instead you pick a base tile,
|
|
size, and shape, then it figures out the rest from there. However, you may
|
|
recall back with the display control register something about an "object memory
|
|
1d" bit. This is where that comes into play.</p>
|
|
<ul>
|
|
<li>If object memory is set to be 2d (the default) then each charblock is treated
|
|
as 32 tiles by 32 tiles square. Each object has a base tile and dimensions,
|
|
and that just extracts directly from the charblock picture as if you were
|
|
selecting an area. This mode probably makes for the easiest image editing.</li>
|
|
<li>If object memory is set to be 1d then the tiles are loaded sequentially from
|
|
the starting point, enough to fill in the object's dimensions. This most
|
|
probably makes it the easiest to program with about things, since programming
|
|
languages are pretty good at 1d things.</li>
|
|
</ul>
|
|
<p>I'm not sure I explained that well, here's a picture:</p>
|
|
<p><img src="obj_memory_2d1d.jpg" alt="2d1d-diagram" /></p>
|
|
<p>In 2d mode, a new row of tiles starts every 32 tile indexes.</p>
|
|
<p>Of course, the mode that you actually end up using is not particularly
|
|
important, since it should be the job of your image conversion routine to get
|
|
everything all lined up and into place anyway.</p>
|
|
<a class="header" href="#set-the-object-attributes" id="set-the-object-attributes"><h2>Set the Object Attributes</h2></a>
|
|
<p>The final step is to assign the correct attributes to an object. Each object has
|
|
three <code>u16</code> values that make up its overall attributes.</p>
|
|
<p>Before we go into the details, I want to remind you that the hardware will
|
|
attempt to process every single object every single frame, and also that all of
|
|
the GBA's memory is cleared to 0 at startup. Why do these two things matter
|
|
right now? As you'll see in a second an "all zero" set of object attributes
|
|
causes an 8x8 object to appear at 0,0 using object tile index 0. This is usually
|
|
<em>not</em> what you want your unused objects to do. When your game first starts you
|
|
should take a moment to mark any objects you won't be using as objects to not
|
|
render.</p>
|
|
<a class="header" href="#objectattributesattr0" id="objectattributesattr0"><h3>ObjectAttributes.attr0</h3></a>
|
|
<ul>
|
|
<li>8 bits for row coordinate (marks the top of the sprite)</li>
|
|
<li>2 bits for object rendering: 0 = Normal, 1 = Affine, 2 = Disabled, 3 = Affine with double rendering area</li>
|
|
<li>2 bits for object mode: 0 = Normal, 1 = Alpha Blending, 2 = Object Window, 3 = Forbidden</li>
|
|
<li>1 bit for mosaic enabled</li>
|
|
<li>1 bit 8bpp color enabled</li>
|
|
<li>2 bits for shape: 0 = Square, 1 = Horizontal, 2 = Vertical, 3 = Forbidden</li>
|
|
</ul>
|
|
<p>If an object is 128 pixels big at Y > 128 you'll get a strange looking result
|
|
where it acts like Y > -128 and then displays partly off screen to the top.</p>
|
|
<a class="header" href="#objectattributesattr1" id="objectattributesattr1"><h3>ObjectAttributes.attr1</h3></a>
|
|
<ul>
|
|
<li>9 bit for column coordinate (marks the left of the sprite)</li>
|
|
<li>Either:
|
|
<ul>
|
|
<li>3 empty bits, 1 bit for horizontal flip, 1 bit for vertical flip (non-affine)</li>
|
|
<li>5 bits for affine index (affine)</li>
|
|
</ul>
|
|
</li>
|
|
<li>2 bits for size.</li>
|
|
</ul>
|
|
<table><thead><tr><th align="center"> Size </th><th align="center"> Square </th><th align="center"> Horizontal </th><th align="center"> Vertical</th></tr></thead><tbody>
|
|
<tr><td align="center"> 0 </td><td align="center"> 8x8 </td><td align="center"> 16x8 </td><td align="center"> 8x16 </td></tr>
|
|
<tr><td align="center"> 1 </td><td align="center"> 16x16 </td><td align="center"> 32x8 </td><td align="center"> 8x32 </td></tr>
|
|
<tr><td align="center"> 2 </td><td align="center"> 32x32 </td><td align="center"> 32x16 </td><td align="center"> 16x32 </td></tr>
|
|
<tr><td align="center"> 3 </td><td align="center"> 64x64 </td><td align="center"> 64x32 </td><td align="center"> 32x64 </td></tr>
|
|
</tbody></table>
|
|
<a class="header" href="#objectattributesattr2" id="objectattributesattr2"><h3>ObjectAttributes.attr2</h3></a>
|
|
<ul>
|
|
<li>10 bits for the base tile index</li>
|
|
<li>2 bits for priority</li>
|
|
<li>4 bits for the palbank index (4bpp mode only, ignored in 8bpp)</li>
|
|
</ul>
|
|
<a class="header" href="#objectattributes-summary" id="objectattributes-summary"><h3>ObjectAttributes summary</h3></a>
|
|
<p>So I said in the GBA memory mapping section that C people would tell you that
|
|
the object attributes should look like this:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[repr(C)]
|
|
pub struct ObjectAttributes {
|
|
attr0: u16,
|
|
attr1: u16,
|
|
attr2: u16,
|
|
filler: i16,
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Except that:</p>
|
|
<ol>
|
|
<li>It's wasteful when we store object attributes on their own outside of OAM
|
|
(which we definitely might want to do).</li>
|
|
<li>In Rust we can't access just one field through a volatile pointer (our
|
|
pointers aren't actually volatile to begin with, just the ops we do with them
|
|
are). We have to read or write the whole pointer's value at a time.
|
|
Similarly, we can't do things like <code>|=</code> and <code>&=</code> with volatile in Rust. So in
|
|
rust we can't have a volatile pointer to an ObjectAttributes and then write
|
|
to just the three "real" values and not touch the filler field. Having the
|
|
filler value in there just means we have to dance around it more, not less.</li>
|
|
<li>We want to newtype this whole thing to prevent accidental invalid states from
|
|
being written into memory.</li>
|
|
</ol>
|
|
<p>So we will not be using that representation. At the same time we want to have no
|
|
overhead, so we will stick to three <code>u16</code> values. We could newtype each
|
|
individual field to be its own type (<code>ObjectAttributesAttr0</code> or something silly
|
|
like that), since there aren't actual dependencies between two different fields
|
|
such that a change in one can throw another into a forbidden state. The worst
|
|
that can happen is if we disable or enable affine mode (<code>attr0</code>) it can change
|
|
the meaning of <code>attr1</code>. The changed meaning isn't actually in invalid state
|
|
though, so we <em>could</em> make each field its own type if we wanted.</p>
|
|
<p>However, when you think about it, I can't imagine a common situation where we do
|
|
something like make an <code>attr0</code> value that we then want to save on its own and
|
|
apply to several different <code>ObjectAttributes</code> that we make during a game. That
|
|
just doesn't sound likely to me. So, we'll go the route where <code>ObjectAttributes</code>
|
|
is just a big black box to the outside world and we don't need to think about
|
|
the three fields internally as being separate.</p>
|
|
<p>First we make it so that we can get and set object attributes from memory:</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
pub const OAM: usize = 0x700_0000;
|
|
|
|
pub fn object_attributes(slot: usize) -> ObjectAttributes {
|
|
assert!(slot < 128);
|
|
let ptr = VolatilePtr((OAM + slot * (size_of::<u16>() * 4)) as *mut u16);
|
|
unsafe {
|
|
ObjectAttributes {
|
|
attr0: ptr.read(),
|
|
attr1: ptr.offset(1).read(),
|
|
attr2: ptr.offset(2).read(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn set_object_attributes(slot: usize, obj: ObjectAttributes) {
|
|
assert!(slot < 128);
|
|
let ptr = VolatilePtr((OAM + slot * (size_of::<u16>() * 4)) as *mut u16);
|
|
unsafe {
|
|
ptr.write(obj.attr0);
|
|
ptr.offset(1).write(obj.attr1);
|
|
ptr.offset(2).write(obj.attr2);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
pub struct ObjectAttributes {
|
|
attr0: u16,
|
|
attr1: u16,
|
|
attr2: u16,
|
|
}
|
|
#}</code></pre></pre>
|
|
<p>Then we add a billion methods to the <code>ObjectAttributes</code> type so that we can
|
|
actually set all the different values that we want to set.</p>
|
|
<p>This code block is the last thing on this page so if you don't wanna scroll past
|
|
the whole thing you can just go to the next page.</p>
|
|
<pre><pre class="playpen"><code class="language-rust">
|
|
# #![allow(unused_variables)]
|
|
#fn main() {
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ObjectRenderMode {
|
|
Normal,
|
|
Affine,
|
|
Disabled,
|
|
DoubleAreaAffine,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ObjectMode {
|
|
Normal,
|
|
AlphaBlending,
|
|
ObjectWindow,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ObjectShape {
|
|
Square,
|
|
Horizontal,
|
|
Vertical,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ObjectOrientation {
|
|
Normal,
|
|
HFlip,
|
|
VFlip,
|
|
BothFlip,
|
|
Affine(u8),
|
|
}
|
|
|
|
impl ObjectAttributes {
|
|
pub fn row(&self) -> u16 {
|
|
self.attr0 & 0b1111_1111
|
|
}
|
|
pub fn column(&self) -> u16 {
|
|
self.attr1 & 0b1_1111_1111
|
|
}
|
|
pub fn rendering(&self) -> ObjectRenderMode {
|
|
match (self.attr0 >> 8) & 0b11 {
|
|
0 => ObjectRenderMode::Normal,
|
|
1 => ObjectRenderMode::Affine,
|
|
2 => ObjectRenderMode::Disabled,
|
|
3 => ObjectRenderMode::DoubleAreaAffine,
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
pub fn mode(&self) -> ObjectMode {
|
|
match (self.attr0 >> 0xA) & 0b11 {
|
|
0 => ObjectMode::Normal,
|
|
1 => ObjectMode::AlphaBlending,
|
|
2 => ObjectMode::ObjectWindow,
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
pub fn mosaic(&self) -> bool {
|
|
((self.attr0 << 3) as i16) < 0
|
|
}
|
|
pub fn two_fifty_six_colors(&self) -> bool {
|
|
((self.attr0 << 2) as i16) < 0
|
|
}
|
|
pub fn shape(&self) -> ObjectShape {
|
|
match (self.attr0 >> 0xE) & 0b11 {
|
|
0 => ObjectShape::Square,
|
|
1 => ObjectShape::Horizontal,
|
|
2 => ObjectShape::Vertical,
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
pub fn orientation(&self) -> ObjectOrientation {
|
|
if (self.attr0 >> 8) & 1 > 0 {
|
|
ObjectOrientation::Affine((self.attr1 >> 9) as u8 & 0b1_1111)
|
|
} else {
|
|
match (self.attr1 >> 0xC) & 0b11 {
|
|
0 => ObjectOrientation::Normal,
|
|
1 => ObjectOrientation::HFlip,
|
|
2 => ObjectOrientation::VFlip,
|
|
3 => ObjectOrientation::BothFlip,
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
}
|
|
pub fn size(&self) -> u16 {
|
|
self.attr1 >> 0xE
|
|
}
|
|
pub fn tile_index(&self) -> u16 {
|
|
self.attr2 & 0b11_1111_1111
|
|
}
|
|
pub fn priority(&self) -> u16 {
|
|
self.attr2 >> 0xA
|
|
}
|
|
pub fn palbank(&self) -> u16 {
|
|
self.attr2 >> 0xC
|
|
}
|
|
//
|
|
pub fn set_row(&mut self, row: u16) {
|
|
self.attr0 &= !0b1111_1111;
|
|
self.attr0 |= row & 0b1111_1111;
|
|
}
|
|
pub fn set_column(&mut self, col: u16) {
|
|
self.attr1 &= !0b1_1111_1111;
|
|
self.attr2 |= col & 0b1_1111_1111;
|
|
}
|
|
pub fn set_rendering(&mut self, rendering: ObjectRenderMode) {
|
|
const RENDERING_MASK: u16 = 0b11 << 8;
|
|
self.attr0 &= !RENDERING_MASK;
|
|
self.attr0 |= (rendering as u16) << 8;
|
|
}
|
|
pub fn set_mode(&mut self, mode: ObjectMode) {
|
|
const MODE_MASK: u16 = 0b11 << 0xA;
|
|
self.attr0 &= MODE_MASK;
|
|
self.attr0 |= (mode as u16) << 0xA;
|
|
}
|
|
pub fn set_mosaic(&mut self, bit: bool) {
|
|
const MOSAIC_BIT: u16 = 1 << 0xC;
|
|
if bit {
|
|
self.attr0 |= MOSAIC_BIT
|
|
} else {
|
|
self.attr0 &= !MOSAIC_BIT
|
|
}
|
|
}
|
|
pub fn set_two_fifty_six_colors(&mut self, bit: bool) {
|
|
const COLOR_MODE_BIT: u16 = 1 << 0xD;
|
|
if bit {
|
|
self.attr0 |= COLOR_MODE_BIT
|
|
} else {
|
|
self.attr0 &= !COLOR_MODE_BIT
|
|
}
|
|
}
|
|
pub fn set_shape(&mut self, shape: ObjectShape) {
|
|
self.attr0 &= 0b0011_1111_1111_1111;
|
|
self.attr0 |= (shape as u16) << 0xE;
|
|
}
|
|
pub fn set_orientation(&mut self, orientation: ObjectOrientation) {
|
|
const AFFINE_INDEX_MASK: u16 = 0b1_1111 << 9;
|
|
self.attr1 &= !AFFINE_INDEX_MASK;
|
|
let bits = match orientation {
|
|
ObjectOrientation::Affine(index) => (index as u16) << 9,
|
|
ObjectOrientation::Normal => 0,
|
|
ObjectOrientation::HFlip => 1 << 0xC,
|
|
ObjectOrientation::VFlip => 1 << 0xD,
|
|
ObjectOrientation::BothFlip => 0b11 << 0xC,
|
|
};
|
|
self.attr1 |= bits;
|
|
}
|
|
pub fn set_size(&mut self, size: u16) {
|
|
self.attr1 &= 0b0011_1111_1111_1111;
|
|
self.attr1 |= size << 14;
|
|
}
|
|
pub fn set_tile_index(&mut self, index: u16) {
|
|
self.attr2 &= !0b11_1111_1111;
|
|
self.attr2 |= 0b11_1111_1111 & index;
|
|
}
|
|
pub fn set_priority(&mut self, priority: u16) {
|
|
self.attr2 &= !0b0000_1100_0000_0000;
|
|
self.attr2 |= (priority & 0b11) << 0xA;
|
|
}
|
|
pub fn set_palbank(&mut self, palbank: u16) {
|
|
self.attr2 &= !0b1111_0000_0000_0000;
|
|
self.attr2 |= (palbank & 0b1111) << 0xC;
|
|
}
|
|
}
|
|
#}</code></pre></pre>
|
|
<a class="header" href="#gba-rng" id="gba-rng"><h1>GBA RNG</h1></a>
|
|
<p>TODO</p>
|
|
<a class="header" href="#memory_game" id="memory_game"><h1>memory_game</h1></a>
|
|
<p>TODO</p>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
|
|
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
|
|
|
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
|
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
|
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
window.addEventListener('load', function() {
|
|
window.setTimeout(window.print, 100);
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|