friggin do it

This commit is contained in:
Lokathor 2018-11-23 14:52:29 -07:00
parent 2196c15fa9
commit 1036b42f76
5 changed files with 363 additions and 79 deletions

View file

@ -248,23 +248,10 @@ GBA's CPU <em>can't do any of that</em>. On the GBA, there's a genuine speed dif
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
<<<<<<< HEAD
<<<<<<< HEAD
be an expensive step. If you've got a 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>
=======
=======
>>>>>>> 6b631474faca0645ad16822187c230b813ab6793
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>
<<<<<<< HEAD
>>>>>>> object attribute stuff
=======
>>>>>>> 6b631474faca0645ad16822187c230b813ab6793
<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
@ -372,17 +359,7 @@ separate groups of related registers.</p>
<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>
<<<<<<< HEAD
<<<<<<< HEAD
<li>2 bits for the priority of each background (0 being highest). If two
backgrounds are set to the same priority the the lower numbered background
layer takes prescience.</li>
=======
<li>2 bits for the priority.</li>
>>>>>>> object attribute stuff
=======
<li>2 bits for the priority.</li>
>>>>>>> 6b631474faca0645ad16822187c230b813ab6793
<li>2 bits for &quot;character base block&quot;, 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>

View file

@ -140,11 +140,371 @@
<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>
<p>TODO: tio afero ke mi diris</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 &quot;HBlank interval free&quot; 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&lt;u16&gt; = VolatilePtr(0x500_0200 as *mut u16);
pub fn object_palette(slot: usize) -&gt; u16 {
assert!(slot &lt; 256);
unsafe { PALRAM_OBJECT_BASE.offset(slot as isize).read() }
}
pub fn set_object_palette(slot: usize, color: u16) {
assert!(slot &lt; 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 &quot;object memory
1d&quot; 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 &quot;all zero&quot; 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 &gt; 128 you'll get a strange looking result
where it acts like Y &gt; -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>&amp;=</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 &quot;real&quot; 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) -&gt; ObjectAttributes {
assert!(slot &lt; 128);
let ptr = VolatilePtr((OAM + slot * (size_of::&lt;u16&gt;() * 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 &lt; 128);
let ptr = VolatilePtr((OAM + slot * (size_of::&lt;u16&gt;() * 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(&amp;self) -&gt; u16 {
self.attr0 &amp; 0b1111_1111
}
pub fn column(&amp;self) -&gt; u16 {
self.attr1 &amp; 0b1_1111_1111
}
pub fn rendering(&amp;self) -&gt; ObjectRenderMode {
match (self.attr0 &gt;&gt; 8) &amp; 0b11 {
0 =&gt; ObjectRenderMode::Normal,
1 =&gt; ObjectRenderMode::Affine,
2 =&gt; ObjectRenderMode::Disabled,
3 =&gt; ObjectRenderMode::DoubleAreaAffine,
_ =&gt; unimplemented!(),
}
}
pub fn mode(&amp;self) -&gt; ObjectMode {
match (self.attr0 &gt;&gt; 0xA) &amp; 0b11 {
0 =&gt; ObjectMode::Normal,
1 =&gt; ObjectMode::AlphaBlending,
2 =&gt; ObjectMode::ObjectWindow,
_ =&gt; unimplemented!(),
}
}
pub fn mosaic(&amp;self) -&gt; bool {
((self.attr0 &lt;&lt; 3) as i16) &lt; 0
}
pub fn two_fifty_six_colors(&amp;self) -&gt; bool {
((self.attr0 &lt;&lt; 2) as i16) &lt; 0
}
pub fn shape(&amp;self) -&gt; ObjectShape {
match (self.attr0 &gt;&gt; 0xE) &amp; 0b11 {
0 =&gt; ObjectShape::Square,
1 =&gt; ObjectShape::Horizontal,
2 =&gt; ObjectShape::Vertical,
_ =&gt; unimplemented!(),
}
}
pub fn orientation(&amp;self) -&gt; ObjectOrientation {
if (self.attr0 &gt;&gt; 8) &amp; 1 &gt; 0 {
ObjectOrientation::Affine((self.attr1 &gt;&gt; 9) as u8 &amp; 0b1_1111)
} else {
match (self.attr1 &gt;&gt; 0xC) &amp; 0b11 {
0 =&gt; ObjectOrientation::Normal,
1 =&gt; ObjectOrientation::HFlip,
2 =&gt; ObjectOrientation::VFlip,
3 =&gt; ObjectOrientation::BothFlip,
}
}
}
pub fn size(&amp;self) -&gt; u16 {
self.attr1 &gt;&gt; 0xE
}
pub fn tile_index(&amp;self) -&gt; u16 {
self.attr2 &amp; 0b11_1111_1111
}
pub fn priority(&amp;self) -&gt; u16 {
self.attr2 &gt;&gt; 0xA
}
pub fn palbank(&amp;self) -&gt; u16 {
self.attr2 &gt;&gt; 0xC
}
//
pub fn set_row(&amp;mut self, row: u16) {
self.attr0 &amp;= !0b1111_1111;
self.attr0 |= row &amp; 0b1111_1111;
}
pub fn set_column(&amp;mut self, col: u16) {
self.attr1 &amp;= !0b1_1111_1111;
self.attr2 |= col &amp; 0b1_1111_1111;
}
pub fn set_rendering(&amp;mut self, rendering: ObjectRenderMode) {
const RENDERING_MASK: u16 = 0b11 &lt;&lt; 8;
self.attr0 &amp;= !RENDERING_MASK;
self.attr0 |= (rendering as u16) &lt;&lt; 8;
}
pub fn set_mode(&amp;mut self, mode: ObjectMode) {
const MODE_MASK: u16 = 0b11 &lt;&lt; 0xA;
self.attr0 &amp;= MODE_MASK;
self.attr0 |= (mode as u16) &lt;&lt; 0xA;
}
pub fn set_mosaic(&amp;mut self, bit: bool) {
const MOSAIC_BIT: u16 = 1 &lt;&lt; 0xC;
if bit {
self.attr0 |= MOSAIC_BIT
} else {
self.attr0 &amp;= !MOSAIC_BIT
}
}
pub fn set_two_fifty_six_colors(&amp;mut self, bit: bool) {
const COLOR_MODE_BIT: u16 = 1 &lt;&lt; 0xD;
if bit {
self.attr0 |= COLOR_MODE_BIT
} else {
self.attr0 &amp;= !COLOR_MODE_BIT
}
}
pub fn set_shape(&amp;mut self, shape: ObjectShape) {
self.attr0 &amp;= 0b0011_1111_1111_1111;
self.attr0 |= (shape as u16) &lt;&lt; 0xE;
}
pub fn set_orientation(&amp;mut self, orientation: ObjectOrientation) {
const AFFINE_INDEX_MASK: u16 = 0b1_1111 &lt;&lt; 9;
self.attr1 &amp;= !AFFINE_INDEX_MASK;
let bits = match orientation {
ObjectOrientation::Affine(index) =&gt; (index as u16) &lt;&lt; 9,
ObjectOrientation::Normal =&gt; 0,
ObjectOrientation::HFlip =&gt; 1 &lt;&lt; 0xC,
ObjectOrientation::VFlip =&gt; 1 &lt;&lt; 0xD,
ObjectOrientation::BothFlip =&gt; 0b11 &lt;&lt; 0xC,
};
self.attr1 |= bits;
}
pub fn set_size(&amp;mut self, size: u16) {
self.attr1 &amp;= 0b0011_1111_1111_1111;
self.attr1 |= size &lt;&lt; 14;
}
pub fn set_tile_index(&amp;mut self, index: u16) {
self.attr2 &amp;= !0b11_1111_1111;
self.attr2 |= 0b11_1111_1111 &amp; index;
}
pub fn set_priority(&amp;mut self, priority: u16) {
self.attr2 &amp;= !0b0000_1100_0000_0000;
self.attr2 |= (priority &amp; 0b11) &lt;&lt; 0xA;
}
pub fn set_palbank(&amp;mut self, palbank: u16) {
self.attr2 &amp;= !0b1111_0000_0000_0000;
self.attr2 |= (palbank &amp; 0b1111) &lt;&lt; 0xC;
}
}
#}</code></pre></pre>
</main>

View file

@ -1772,23 +1772,10 @@ GBA's CPU <em>can't do any of that</em>. On the GBA, there's a genuine speed dif
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
<<<<<<< HEAD
<<<<<<< HEAD
be an expensive step. If you've got a 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>
=======
=======
>>>>>>> 6b631474faca0645ad16822187c230b813ab6793
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>
<<<<<<< HEAD
>>>>>>> object attribute stuff
=======
>>>>>>> 6b631474faca0645ad16822187c230b813ab6793
<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
@ -1896,17 +1883,7 @@ separate groups of related registers.</p>
<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>
<<<<<<< HEAD
<<<<<<< HEAD
<li>2 bits for the priority of each background (0 being highest). If two
backgrounds are set to the same priority the the lower numbered background
layer takes prescience.</li>
=======
<li>2 bits for the priority.</li>
>>>>>>> object attribute stuff
=======
<li>2 bits for the priority.</li>
>>>>>>> 6b631474faca0645ad16822187c230b813ab6793
<li>2 bits for &quot;character base block&quot;, 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>
@ -1972,16 +1949,6 @@ target pixel. You can't change the target pixel on a block by block basis.</p>
<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>
<<<<<<< HEAD
<<<<<<< HEAD
<p>TODO: tio afero ke mi diris</p>
<a class="header" href="#objects-vs-sprites" id="objects-vs-sprites"><h2>Objects vs Sprites</h2></a>
<a class="header" href="#ready-the-palette" id="ready-the-palette"><h2>Ready the Palette</h2></a>
<a class="header" href="#ready-the-tiles" id="ready-the-tiles"><h2>Ready the Tiles</h2></a>
<a class="header" href="#set-the-object-attributes" id="set-the-object-attributes"><h2>Set the Object Attributes</h2></a>
=======
=======
>>>>>>> 6b631474faca0645ad16822187c230b813ab6793
<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
@ -2347,10 +2314,6 @@ impl ObjectAttributes {
}
}
#}</code></pre></pre>
<<<<<<< HEAD
>>>>>>> object attribute stuff
=======
>>>>>>> 6b631474faca0645ad16822187c230b813ab6793
<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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long