gba/docs/ch03/tile_data.html

315 lines
18 KiB
HTML
Raw Normal View History

2018-11-19 16:19:13 +11:00
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
2018-11-20 20:57:43 +11:00
<title>Tile Data - Rust GBA Guide</title>
2018-11-19 16:19:13 +11:00
<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">
2018-11-20 20:57:43 +11:00
<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" class="active"><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/object_basics.html"><strong aria-hidden="true">5.4.</strong> Object Basics</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>
2018-11-19 16:19:13 +11:00
</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>
2018-11-20 20:57:43 +11:00
<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 &quot;character data&quot;. In fact
some guides will even call the regular mode for the background layers &quot;text
mode&quot;, 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>
2018-11-19 16:19:13 +11:00
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
2018-11-20 20:57:43 +11:00
<a rel="prev" href="../ch03/gba_memory_mapping.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
2018-11-19 16:19:13 +11:00
<i class="fa fa-angle-left"></i>
</a>
2018-11-20 20:57:43 +11:00
<a rel="next" href="../ch03/regular_backgrounds.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
2018-11-19 16:19:13 +11:00
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
2018-11-20 20:57:43 +11:00
<a href="../ch03/gba_memory_mapping.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
2018-11-19 16:19:13 +11:00
<i class="fa fa-angle-left"></i>
</a>
2018-11-20 20:57:43 +11:00
<a href="../ch03/regular_backgrounds.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
2018-11-19 16:19:13 +11:00
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</body>
</html>