gba/docs/ch01/video_memory_intro.html

297 lines
17 KiB
HTML
Raw Normal View History

2018-11-08 15:20:40 +11:00
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
2018-11-18 11:14:42 +11:00
<title>Video Memory Intro - Rust GBA Guide</title>
2018-11-08 15:20:40 +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" />
2018-11-10 20:03:37 +11:00
<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">
2018-11-08 15:20:40 +11:00
<!-- Fonts -->
2018-11-10 20:03:37 +11:00
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
2018-11-08 15:20:40 +11:00
<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 -->
2018-11-10 20:03:37 +11:00
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
2018-11-08 15:20:40 +11:00
<!-- Custom theme stylesheets -->
</head>
<body class="light">
<!-- Provide site root to javascript -->
2018-11-10 20:03:37 +11:00
<script type="text/javascript">var path_to_root = "../";</script>
2018-11-08 15:20:40 +11:00
<!-- 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" class="active"><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/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-08 15:20:40 +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>
2018-11-18 11:14:42 +11:00
<h1 class="menu-title">Rust GBA Guide</h1>
2018-11-08 15:20:40 +11:00
<div class="right-buttons">
2018-11-10 20:03:37 +11:00
<a href="../print.html" title="Print this book" aria-label="Print this book">
2018-11-08 15:20:40 +11:00
<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-10 20:03:37 +11:00
<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>
2018-11-14 04:32:27 +11:00
<p>The Video RAM can only be accessed totally freely during a Vertical Blank (aka
&quot;VBlank&quot;, 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>
2018-11-10 20:03:37 +11:00
<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>
2018-11-11 17:39:26 +11:00
<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>
2018-11-10 20:03:37 +11:00
<a class="header" href="#mode-3" id="mode-3"><h2>Mode 3</h2></a>
2018-11-11 17:39:26 +11:00
<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 &quot;2d indexing&quot; 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>
2018-11-10 20:03:37 +11:00
<a class="header" href="#mode-4" id="mode-4"><h2>Mode 4</h2></a>
2018-11-11 17:39:26 +11:00
<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 &quot;minimum size&quot; 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>
2018-11-10 20:03:37 +11:00
<a class="header" href="#mode-5" id="mode-5"><h2>Mode 5</h2></a>
2018-11-11 17:39:26 +11:00
<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>
2018-11-10 20:03:37 +11:00
<a class="header" href="#in-conclusion" id="in-conclusion"><h2>In Conclusion...</h2></a>
2018-11-11 17:39:26 +11:00
<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>
2018-11-19 16:19:13 +11:00
<li>0x001F: 0b0_00000_00000_11111</li>
<li>0x03E0: 0b0_00000_11111_00000</li>
<li>0x7C00: 0b0_11111_00000_00000</li>
2018-11-11 17:39:26 +11:00
</ul>
<p>Ah, of course, a red pixel, a green pixel, and a blue pixel.</p>
2018-11-08 15:20:40 +11:00
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ch01/the_display_control_register.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
2018-11-10 20:03:37 +11:00
<i class="fa fa-angle-left"></i>
</a>
2018-11-08 15:20:40 +11:00
<a rel="next" href="../ch01/hello2.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
2018-11-11 17:39:26 +11:00
<i class="fa fa-angle-right"></i>
</a>
2018-11-08 15:20:40 +11:00
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="../ch01/the_display_control_register.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
2018-11-10 20:03:37 +11:00
<i class="fa fa-angle-left"></i>
</a>
2018-11-08 15:20:40 +11:00
<a href="../ch01/hello2.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
2018-11-11 17:39:26 +11:00
<i class="fa fa-angle-right"></i>
</a>
2018-11-08 15:20:40 +11:00
</nav>
</div>
2018-11-10 20:03:37 +11:00
<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>
2018-11-08 15:20:40 +11:00
2018-11-10 20:03:37 +11:00
<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>
2018-11-08 15:20:40 +11:00
<!-- Custom JS scripts -->
</body>
</html>