mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 19:01:30 +11:00
io registers lesson, +cargo fmt with lin length down to 100
This commit is contained in:
parent
1094794ef8
commit
b67bdc80a1
|
@ -1 +1,230 @@
|
||||||
# IO Registers
|
# IO Registers
|
||||||
|
|
||||||
|
As I said before, the IO registers are how you tell the GBA to do all the things
|
||||||
|
you want it to do. If you want a hint at what's available, they're all listed
|
||||||
|
out in the [GBA I/O Map](https://problemkaputt.de/gbatek.htm#gbaiomap) section
|
||||||
|
of GBATEK. Go have a quick look.
|
||||||
|
|
||||||
|
Each individual IO register has a particular address just like we talked about
|
||||||
|
in the Hardware Memory Map section. They also have a size (listed in bytes), and
|
||||||
|
a note on if they're read only, write only, or read-write. Finally, each
|
||||||
|
register has a name and a one line summary. Unfortunately for us, the names are
|
||||||
|
all C style names with heavy shorthand. I'm not normally a fan of shorthand
|
||||||
|
names, but the `gba` crate uses the register names from GBATEK as much as
|
||||||
|
possible, since they're the most commonly used set of names among GBA
|
||||||
|
programmers. That way, if you're reading other guides and they say to set the
|
||||||
|
`BG2CNT` register, then you know exactly what register to look for within the
|
||||||
|
`gba` docs.
|
||||||
|
|
||||||
|
## Register Bits
|
||||||
|
|
||||||
|
There's only about 100 registers, but there's a lot more than 100 details we
|
||||||
|
want to have control over on the GBA. How does that work? Well, let's use a
|
||||||
|
particular register to talk about it. The first one on the list is `DISPCNT`,
|
||||||
|
the "Display Control" register. It's one of the most important IO registers, so
|
||||||
|
this is a "two birds with one stone" situation.
|
||||||
|
|
||||||
|
Naturally there's a whole lot of things involved in the LCD that we want to
|
||||||
|
control, and it's all "one" value, but that value is actually many "fields"
|
||||||
|
packed into one value. When learning about an IO register, you have to look at
|
||||||
|
its bit pattern breakdown. For `DISPCNT` the GBATEK entry looks like this:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
4000000h - DISPCNT - LCD Control (Read/Write)
|
||||||
|
Bit Expl.
|
||||||
|
0-2 BG Mode (0-5=Video Mode 0-5, 6-7=Prohibited)
|
||||||
|
3 Reserved / CGB Mode (0=GBA, 1=CGB; can be set only by BIOS opcodes)
|
||||||
|
4 Display Frame Select (0-1=Frame 0-1) (for BG Modes 4,5 only)
|
||||||
|
5 H-Blank Interval Free (1=Allow access to OAM during H-Blank)
|
||||||
|
6 OBJ Character VRAM Mapping (0=Two dimensional, 1=One dimensional)
|
||||||
|
7 Forced Blank (1=Allow FAST access to VRAM,Palette,OAM)
|
||||||
|
8 Screen Display BG0 (0=Off, 1=On)
|
||||||
|
9 Screen Display BG1 (0=Off, 1=On)
|
||||||
|
10 Screen Display BG2 (0=Off, 1=On)
|
||||||
|
11 Screen Display BG3 (0=Off, 1=On)
|
||||||
|
12 Screen Display OBJ (0=Off, 1=On)
|
||||||
|
13 Window 0 Display Flag (0=Off, 1=On)
|
||||||
|
14 Window 1 Display Flag (0=Off, 1=On)
|
||||||
|
15 OBJ Window Display Flag (0=Off, 1=On)
|
||||||
|
```
|
||||||
|
|
||||||
|
So what we're supposed to understand here is that we've got a `u16`, and then we
|
||||||
|
set the individual bits for the things that we want. In the `hello_magic`
|
||||||
|
example you might recall that we set this register to the value `0x0403`. That
|
||||||
|
was a bit of a trick on my part because hex numbers usually look far more
|
||||||
|
mysterious than decimal or binary numbers. If we converted it to binary it'd
|
||||||
|
look like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
0b100_0000_0011
|
||||||
|
```
|
||||||
|
|
||||||
|
And then you can just go down the list of settings to see what bits are what:
|
||||||
|
|
||||||
|
* Bits 0-2 (BG Mode) are `0b011`, so that's Video Mode 3
|
||||||
|
* Bit 10 (Display BG2) is enabled
|
||||||
|
* Everything else is disabled
|
||||||
|
|
||||||
|
Naturally, trying to remember exactly what bit does what can be difficult. In
|
||||||
|
the `gba` crate we attempt as much as possible to make types that wrap over a
|
||||||
|
`u16` or `u32` and then have getters and setters _as if_ all the inner bits were
|
||||||
|
different fields.
|
||||||
|
|
||||||
|
* If it's a single bit then the getter/setter will use `bool`.
|
||||||
|
* If it's more than one bit and each pattern has some non-numeric meaning then
|
||||||
|
it'll use an `enum`.
|
||||||
|
* If it's more than one bit and numeric in nature then it'll just use the
|
||||||
|
wrapped integer type. Note that you generally won't get the full range of the
|
||||||
|
inner number type, and any excess gets truncated down to fit in the bits
|
||||||
|
available.
|
||||||
|
|
||||||
|
All the getters and setters are defined as `const` functions, so you can make
|
||||||
|
constant declarations for the exact setting combinations that you want.
|
||||||
|
|
||||||
|
## Some Important IO Registers
|
||||||
|
|
||||||
|
It's not easy to automatically see what registers will be important for getting
|
||||||
|
started and what registers can be saved to learn about later.
|
||||||
|
|
||||||
|
We'll go over three IO registers here that will help us the most to get started,
|
||||||
|
then next lesson we'll cover how that Video Mode 3 bitmap drawing works, and
|
||||||
|
then by the end of the next lesson we'll be able to put it all together into
|
||||||
|
something interactive.
|
||||||
|
|
||||||
|
### DISPCNT: Display Control
|
||||||
|
|
||||||
|
The [DISPCNT](https://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol) register
|
||||||
|
lets us affect the major details of our video output. There's a lot of other
|
||||||
|
registers involved too, but it all starts here.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new(0x400_0000) };
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, the display control register is, like most registers,
|
||||||
|
complicated enough that we make it a dedicated type with getters and setters for
|
||||||
|
the "phantom" fields. In this case it's mostly a bunch of `bool` values we can
|
||||||
|
set, and also the video mode is an `enum`.
|
||||||
|
|
||||||
|
We already looked at the bit listing above, let's go over what's important right
|
||||||
|
now and skip the other bits:
|
||||||
|
|
||||||
|
* BG Mode sets how the whole screen is going to work and even how the display
|
||||||
|
adapter is going to interpret the bit layout of video memory for pixel
|
||||||
|
processing. We'll start with Mode 3, which is the simplest to learn.
|
||||||
|
* The "Forced Blank" bit is one of the very few bits that starts _on_ at the
|
||||||
|
start of the main program. When it's enabled it prevents the display adapter
|
||||||
|
from displaying anything at all. You use this bit when you need to do a very
|
||||||
|
long change to video memory and you don't want the user to see the
|
||||||
|
intermediate states being partly drawn.
|
||||||
|
* The "Screen Display" bits let us enable different display layers. We care
|
||||||
|
about BG2 right now because the bitmap modes (3, 4, and 5) are all treated as
|
||||||
|
if they were drawing into BG2 (even though it's the only BG layer available in
|
||||||
|
those modes).
|
||||||
|
|
||||||
|
There's a bunch of other stuff, but we'll get to those things later.
|
||||||
|
|
||||||
|
### VCOUNT: Vertical Display Counter
|
||||||
|
|
||||||
|
The [VCOUNT](https://problemkaputt.de/gbatek.htm#lcdiointerruptsandstatus)
|
||||||
|
register lets us find out what row of pixels (called a **scanline**) is
|
||||||
|
currently being processed.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const VCOUNT: ROVolAddress<u16> = unsafe { ROVolAddress::new(0x400_0006) };
|
||||||
|
```
|
||||||
|
|
||||||
|
You see, the display adapter is constantly running its own loop, along side the
|
||||||
|
CPU. It starts at the very first pixel of the very first scanline, takes 4
|
||||||
|
cycles to determine what color that pixel is, and then processes the next
|
||||||
|
pixel. Each scanline is 240 pixels long, followed by 68 "virtual" pixels so that
|
||||||
|
you have just a moment to setup for the next scanline to be drawn if you need
|
||||||
|
it. 272 cycles (68*4) is not a lot of time, but it's enough that you could
|
||||||
|
change some palette colors or move some objects around if you need to.
|
||||||
|
|
||||||
|
* Horizontal pixel value `0..240`: "HDraw"
|
||||||
|
* Horizontal pixel value `240..308`: "HBlank"
|
||||||
|
|
||||||
|
There's no way to check the current horizontal counter, but there is a way to
|
||||||
|
have the CPU interrupt the normal code when the HBlank period starts, which
|
||||||
|
we'll learn about later.
|
||||||
|
|
||||||
|
Once a complete scanline has been processed (including the blank period), the
|
||||||
|
display adapter keeps going with the next scanline. Similar to how the
|
||||||
|
horizontal processing works, there's 160 scanlines in the real display, and then
|
||||||
|
it's followed by 68 "virtual" scanlines to give you time for adjusting video
|
||||||
|
memory between the frames of the game.
|
||||||
|
|
||||||
|
* Vertical Count `0..160`: "VDraw"
|
||||||
|
* Vertical Count `160..228`: "VBlank"
|
||||||
|
|
||||||
|
Once every scanline has been processed (including the vblank period), the
|
||||||
|
display adapter starts the whole loop over again with scanline 0. A total of
|
||||||
|
280,896 cycles per display loop (4 * 308 * 228), and about 59.59ns per CPU
|
||||||
|
cycle, gives us a full speed display rate of 59.73fps. That's close enough to
|
||||||
|
60fps that I think we can just round up a bit whenever we're not counting it
|
||||||
|
down to the exact cycle timings.
|
||||||
|
|
||||||
|
However, there's a bit of a snag. If we change video memory during the middle of
|
||||||
|
a scanline the display will _immediately_ start processing using the new state
|
||||||
|
of video memory. The picture before the change and after the change won't look
|
||||||
|
like a single, clean picture. Instead you'll get what's called "[screen
|
||||||
|
tearing](https://en.wikipedia.org/wiki/Screen_tearing)", which is usually
|
||||||
|
considered to be the mark of a badly programmed game.
|
||||||
|
|
||||||
|
To avoid this we just need to only adjust video memory during one of the blank
|
||||||
|
periods. If you're really cool you can adjust things during HBlank, but we're
|
||||||
|
not that cool yet. Starting out our general program flow will be:
|
||||||
|
|
||||||
|
1) Gather input for the frame (next part of this lesson) and update the game
|
||||||
|
state, getting everything ready for when VBlank actually starts.
|
||||||
|
2) Once VBlank starts we update all of the video memory as fast as we can.
|
||||||
|
3) Once we're done drawing we again wait for the VDraw period to begin and then
|
||||||
|
do it all again.
|
||||||
|
|
||||||
|
Now, it's not the most efficient way, but to get our timings right we can just
|
||||||
|
read from `VCOUNT` over and over in a "busy loop". Once we read a value of 160
|
||||||
|
we know that we've entered VBlank. Once it goes back to 0 we know that we're
|
||||||
|
back in VDraw.
|
||||||
|
|
||||||
|
Doing a busy loop like this actually drains the batteries way more than
|
||||||
|
necessary. It keeps the CPU active constantly, which is what uses a fair amount
|
||||||
|
of the power. Normally you're supposed to put the CPU to sleep if you're just
|
||||||
|
waiting around for something to happen. However, that also requires learning
|
||||||
|
about some more concepts to get right. So to keep things easier starting out
|
||||||
|
we'll do the bad/lazy version and then upgrade our technique later.
|
||||||
|
|
||||||
|
### KEYINPUT: Key Input Reading
|
||||||
|
|
||||||
|
The [KEYINPUT](https://problemkaputt.de/gbatek.htm#gbakeypadinput) register is
|
||||||
|
the last one we've got to learn about this lesson. It lets you check the status
|
||||||
|
of all 10 buttons on the GBA.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const KEYINPUT: ROVolAddress<u16> = unsafe { ROVolAddress::new(0x400_0130) };
|
||||||
|
```
|
||||||
|
|
||||||
|
There's little to say here. It's a read only register, and the data just
|
||||||
|
contains one bit per button. The only thing that's a little weird about it is
|
||||||
|
that the bits follow a "low active" convention, so if the button is pressed then
|
||||||
|
the bit is 0, and if the button is released the bit is 1.
|
||||||
|
|
||||||
|
You _could_ work with that directly, but I think it's a lot easier to think
|
||||||
|
about having `true` for pressed and `false` for not pressed. So the `gba` crate
|
||||||
|
flips the bits when you read the keys:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Gets the current state of the keys
|
||||||
|
pub fn read_key_input() -> KeyInput {
|
||||||
|
KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can treat the KeyInput values like a totally normal bitset.
|
||||||
|
|
||||||
|
## De-mystifying `hello_magic`
|
||||||
|
|
||||||
|
So, given what we've learned, we can look again at the `hello_magic` program
|
||||||
|
with some of that stuff replaced to be a more readable form:
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
|
@ -10,9 +10,15 @@ fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
unsafe {
|
unsafe {
|
||||||
(0x400_0000 as *mut u16).write_volatile(0x0403);
|
(0x400_0000 as *mut u16).write_volatile(0x0403);
|
||||||
(0x600_0000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
|
(0x600_0000 as *mut u16)
|
||||||
(0x600_0000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
|
.offset(120 + 80 * 240)
|
||||||
(0x600_0000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
|
.write_volatile(0x001F);
|
||||||
|
(0x600_0000 as *mut u16)
|
||||||
|
.offset(136 + 80 * 240)
|
||||||
|
.write_volatile(0x03E0);
|
||||||
|
(0x600_0000 as *mut u16)
|
||||||
|
.offset(120 + 96 * 240)
|
||||||
|
.write_volatile(0x7C00);
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,5 +4,5 @@ merge_imports = true
|
||||||
reorder_imports = true
|
reorder_imports = true
|
||||||
use_try_shorthand = true
|
use_try_shorthand = true
|
||||||
tab_spaces = 2
|
tab_spaces = 2
|
||||||
max_width = 150
|
max_width = 100
|
||||||
color = "Never"
|
color = "Never"
|
||||||
|
|
|
@ -122,7 +122,7 @@ impl DisplayStatusSetting {
|
||||||
/// Gives the current scanline that the display controller is working on. If
|
/// Gives the current scanline that the display controller is working on. If
|
||||||
/// this is at or above the `VBLANK_SCANLINE` value then the display controller
|
/// this is at or above the `VBLANK_SCANLINE` value then the display controller
|
||||||
/// is in a "vertical blank" period.
|
/// is in a "vertical blank" period.
|
||||||
pub const VCOUNT: VolAddress<u16> = unsafe { VolAddress::new(0x400_0006) };
|
pub const VCOUNT: ROVolAddress<u16> = unsafe { ROVolAddress::new(0x400_0006) };
|
||||||
|
|
||||||
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
/// If the `VCOUNT` register reads equal to or above this then you're in vblank.
|
||||||
pub const VBLANK_SCANLINE: u16 = 160;
|
pub const VBLANK_SCANLINE: u16 = 160;
|
||||||
|
|
|
@ -8,7 +8,7 @@ use super::*;
|
||||||
/// follow the "high-active" convention (hint: you probably do, it's far easier
|
/// follow the "high-active" convention (hint: you probably do, it's far easier
|
||||||
/// to work with) then call `read_key_input()` rather than reading this register
|
/// to work with) then call `read_key_input()` rather than reading this register
|
||||||
/// directly. It will perform the necessary bit flip operation for you.
|
/// directly. It will perform the necessary bit flip operation for you.
|
||||||
pub const KEYINPUT: VolAddress<u16> = unsafe { VolAddress::new(0x400_0130) };
|
pub const KEYINPUT: ROVolAddress<u16> = unsafe { ROVolAddress::new(0x400_0130) };
|
||||||
|
|
||||||
/// A "tribool" value helps us interpret the arrow pad.
|
/// A "tribool" value helps us interpret the arrow pad.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
|
@ -60,7 +60,8 @@ pub const SOUND2CNT_L: VolAddress<DutyLenEnvelopeSetting> = unsafe { VolAddress:
|
||||||
pub const SOUND2CNT_H: VolAddress<FrequencyControlSetting> = unsafe { VolAddress::new(0x400_006C) };
|
pub const SOUND2CNT_H: VolAddress<FrequencyControlSetting> = unsafe { VolAddress::new(0x400_006C) };
|
||||||
|
|
||||||
/// Sound Channel 3 Stop/Wave RAM select (`NR23`, `NR24`). Read/Write.
|
/// Sound Channel 3 Stop/Wave RAM select (`NR23`, `NR24`). Read/Write.
|
||||||
pub const SOUND3CNT_L: VolAddress<StopWaveRAMSelectSetting> = unsafe { VolAddress::new(0x400_0070) };
|
pub const SOUND3CNT_L: VolAddress<StopWaveRAMSelectSetting> =
|
||||||
|
unsafe { VolAddress::new(0x400_0070) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
StopWaveRAMSelectSetting, u16
|
StopWaveRAMSelectSetting, u16
|
||||||
|
@ -158,7 +159,8 @@ pub const FIFO_B_L: VolAddress<u16> = unsafe { VolAddress::new(0x400_00A4) };
|
||||||
pub const FIFO_B_H: VolAddress<u16> = unsafe { VolAddress::new(0x400_00A6) };
|
pub const FIFO_B_H: VolAddress<u16> = unsafe { VolAddress::new(0x400_00A6) };
|
||||||
|
|
||||||
/// Channel L/R Volume/Enable (`NR50`, `NR51`). Read/Write.
|
/// Channel L/R Volume/Enable (`NR50`, `NR51`). Read/Write.
|
||||||
pub const SOUNDCNT_L: VolAddress<NonWaveVolumeEnableSetting> = unsafe { VolAddress::new(0x400_0080) };
|
pub const SOUNDCNT_L: VolAddress<NonWaveVolumeEnableSetting> =
|
||||||
|
unsafe { VolAddress::new(0x400_0080) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
NonWaveVolumeEnableSetting, u16
|
NonWaveVolumeEnableSetting, u16
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
//! do, it's a giant bag of Undefined Behavior.
|
//! do, it's a giant bag of Undefined Behavior.
|
||||||
|
|
||||||
pub(crate) use gba_proc_macro::phantom_fields;
|
pub(crate) use gba_proc_macro::phantom_fields;
|
||||||
pub(crate) use voladdress::{VolAddress, VolBlock};
|
pub(crate) use voladdress::{read_only::ROVolAddress, VolAddress, VolBlock};
|
||||||
|
|
||||||
/// Assists in defining a newtype wrapper over some base type.
|
/// Assists in defining a newtype wrapper over some base type.
|
||||||
///
|
///
|
||||||
|
|
|
@ -157,7 +157,11 @@ pub fn read_obj_attributes(slot: usize) -> Option<ObjectAttributes> {
|
||||||
let attr0 = va_u16.cast::<OBJAttr0>().read();
|
let attr0 = va_u16.cast::<OBJAttr0>().read();
|
||||||
let attr1 = va_u16.offset(1).cast::<OBJAttr1>().read();
|
let attr1 = va_u16.offset(1).cast::<OBJAttr1>().read();
|
||||||
let attr2 = va_u16.offset(2).cast::<OBJAttr2>().read();
|
let attr2 = va_u16.offset(2).cast::<OBJAttr2>().read();
|
||||||
ObjectAttributes { attr0, attr1, attr2 }
|
ObjectAttributes {
|
||||||
|
attr0,
|
||||||
|
attr1,
|
||||||
|
attr2,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ pub const VRAM_BASE_USIZE: usize = 0x600_0000;
|
||||||
pub const CHAR_BASE_BLOCKS: VolBlock<[u8; 0x4000], U6> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
pub const CHAR_BASE_BLOCKS: VolBlock<[u8; 0x4000], U6> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// The screen entry base blocks.
|
/// The screen entry base blocks.
|
||||||
pub const SCREEN_BASE_BLOCKS: VolBlock<[u8; 0x800], U32> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
pub const SCREEN_BASE_BLOCKS: VolBlock<[u8; 0x800], U32> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
newtype! {
|
newtype! {
|
||||||
/// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment.
|
/// An 8x8 tile with 4bpp, packed as `u32` values for proper alignment.
|
||||||
|
|
|
@ -30,10 +30,12 @@ impl Mode3 {
|
||||||
///
|
///
|
||||||
/// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel,
|
/// Use `col + row * SCREEN_WIDTH` to get the address of an individual pixel,
|
||||||
/// or use the helpers provided in this module.
|
/// or use the helpers provided in this module.
|
||||||
pub const VRAM: VolBlock<Color, <U256 as Mul<U160>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
pub const VRAM: VolBlock<Color, <U256 as Mul<U160>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// private iterator over the pixels, two at a time
|
/// private iterator over the pixels, two at a time
|
||||||
const VRAM_BULK: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
const VRAM_BULK: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// Reads the pixel at the given (col,row).
|
/// Reads the pixel at the given (col,row).
|
||||||
///
|
///
|
||||||
|
@ -41,7 +43,9 @@ impl Mode3 {
|
||||||
///
|
///
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds.
|
||||||
pub fn read_pixel(col: usize, row: usize) -> Option<Color> {
|
pub fn read_pixel(col: usize, row: usize) -> Option<Color> {
|
||||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
Self::VRAM
|
||||||
|
.get(col + row * Self::SCREEN_WIDTH)
|
||||||
|
.map(VolAddress::read)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the pixel at the given (col,row).
|
/// Writes the pixel at the given (col,row).
|
||||||
|
@ -50,7 +54,9 @@ impl Mode3 {
|
||||||
///
|
///
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds.
|
||||||
pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> {
|
pub fn write_pixel(col: usize, row: usize, color: Color) -> Option<()> {
|
||||||
Self::VRAM.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
Self::VRAM
|
||||||
|
.get(col + row * Self::SCREEN_WIDTH)
|
||||||
|
.map(|va| va.write(color))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears the whole screen to the desired color.
|
/// Clears the whole screen to the desired color.
|
||||||
|
@ -68,7 +74,13 @@ impl Mode3 {
|
||||||
|
|
||||||
let color32 = color.0 as u32;
|
let color32 = color.0 as u32;
|
||||||
let bulk_color = color32 << 16 | color32;
|
let bulk_color = color32 << 16 | color32;
|
||||||
unsafe { DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, (Self::SCREEN_PIXEL_COUNT / 2) as u16) };
|
unsafe {
|
||||||
|
DMA3::fill32(
|
||||||
|
&bulk_color,
|
||||||
|
VRAM_BASE_USIZE as *mut u32,
|
||||||
|
(Self::SCREEN_PIXEL_COUNT / 2) as u16,
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,22 +114,28 @@ impl Mode4 {
|
||||||
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 4;
|
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 4;
|
||||||
|
|
||||||
// TODO: newtype this?
|
// TODO: newtype this?
|
||||||
const PAGE0_BLOCK8: VolBlock<u8, <U256 as Mul<U160>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
const PAGE0_BLOCK8: VolBlock<u8, <U256 as Mul<U160>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
// TODO: newtype this?
|
||||||
const PAGE1_BLOCK8: VolBlock<u8, <U256 as Mul<U160>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
const PAGE1_BLOCK8: VolBlock<u8, <U256 as Mul<U160>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
// TODO: newtype this?
|
||||||
const PAGE0_BLOCK16: VolBlock<u16, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
const PAGE0_BLOCK16: VolBlock<u16, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
// TODO: newtype this?
|
||||||
const PAGE1_BLOCK16: VolBlock<u16, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
const PAGE1_BLOCK16: VolBlock<u16, <<U256 as Mul<U160>>::Output as Div<U2>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
||||||
|
|
||||||
/// private iterator over the page0 pixels, four at a time
|
/// private iterator over the page0 pixels, four at a time
|
||||||
const PAGE0_BULK32: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
const PAGE0_BULK32: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// private iterator over the page1 pixels, four at a time
|
/// private iterator over the page1 pixels, four at a time
|
||||||
const PAGE1_BULK32: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
const PAGE1_BULK32: VolBlock<u32, <<U256 as Mul<U160>>::Output as Div<U4>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
||||||
|
|
||||||
/// Reads the pixel at the given (col,row).
|
/// Reads the pixel at the given (col,row).
|
||||||
///
|
///
|
||||||
|
@ -127,9 +145,13 @@ impl Mode4 {
|
||||||
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<u8> {
|
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<u8> {
|
||||||
// Note(Lokathor): byte _reads_ from VRAM are okay.
|
// Note(Lokathor): byte _reads_ from VRAM are okay.
|
||||||
if page1 {
|
if page1 {
|
||||||
Self::PAGE1_BLOCK8.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
Self::PAGE1_BLOCK8
|
||||||
|
.get(col + row * Self::SCREEN_WIDTH)
|
||||||
|
.map(VolAddress::read)
|
||||||
} else {
|
} else {
|
||||||
Self::PAGE0_BLOCK8.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
Self::PAGE0_BLOCK8
|
||||||
|
.get(col + row * Self::SCREEN_WIDTH)
|
||||||
|
.map(VolAddress::read)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +193,9 @@ impl Mode4 {
|
||||||
/// The page is imagined to be a series of `u16` values rather than `u8`
|
/// The page is imagined to be a series of `u16` values rather than `u8`
|
||||||
/// values, allowing you to write two palette entries side by side as a single
|
/// values, allowing you to write two palette entries side by side as a single
|
||||||
/// write operation.
|
/// write operation.
|
||||||
pub fn write_wide_pixel(page1: bool, wide_col: usize, row: usize, wide_pal8bpp: u16) -> Option<()> {
|
pub fn write_wide_pixel(
|
||||||
|
page1: bool, wide_col: usize, row: usize, wide_pal8bpp: u16,
|
||||||
|
) -> Option<()> {
|
||||||
if wide_col < Self::SCREEN_WIDTH / 2 && row < Self::SCREEN_HEIGHT {
|
if wide_col < Self::SCREEN_WIDTH / 2 && row < Self::SCREEN_HEIGHT {
|
||||||
let wide_index = wide_col + row * Self::SCREEN_WIDTH / 2;
|
let wide_index = wide_col + row * Self::SCREEN_WIDTH / 2;
|
||||||
let address: VolAddress<u16> = if page1 {
|
let address: VolAddress<u16> = if page1 {
|
||||||
|
@ -189,7 +213,13 @@ impl Mode4 {
|
||||||
pub fn clear_page_to(page1: bool, pal8bpp: u8) {
|
pub fn clear_page_to(page1: bool, pal8bpp: u8) {
|
||||||
let pal8bpp_32 = pal8bpp as u32;
|
let pal8bpp_32 = pal8bpp as u32;
|
||||||
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
let bulk_color = (pal8bpp_32 << 24) | (pal8bpp_32 << 16) | (pal8bpp_32 << 8) | pal8bpp_32;
|
||||||
for va in (if page1 { Self::PAGE1_BULK32 } else { Self::PAGE0_BULK32 }).iter() {
|
for va in (if page1 {
|
||||||
|
Self::PAGE1_BULK32
|
||||||
|
} else {
|
||||||
|
Self::PAGE0_BULK32
|
||||||
|
})
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
va.write(bulk_color)
|
va.write(bulk_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,16 +267,20 @@ impl Mode5 {
|
||||||
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 2;
|
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 2;
|
||||||
|
|
||||||
// TODO: newtype this?
|
// TODO: newtype this?
|
||||||
const PAGE0_BLOCK: VolBlock<Color, <U160 as Mul<U128>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
const PAGE0_BLOCK: VolBlock<Color, <U160 as Mul<U128>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
// TODO: newtype this?
|
// TODO: newtype this?
|
||||||
const PAGE1_BLOCK: VolBlock<Color, <U160 as Mul<U128>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
const PAGE1_BLOCK: VolBlock<Color, <U160 as Mul<U128>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
||||||
|
|
||||||
/// private iterator over the page0 pixels, four at a time
|
/// private iterator over the page0 pixels, four at a time
|
||||||
const PAGE0_BULK32: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
const PAGE0_BULK32: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE) };
|
||||||
|
|
||||||
/// private iterator over the page1 pixels, four at a time
|
/// private iterator over the page1 pixels, four at a time
|
||||||
const PAGE1_BULK32: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> = unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
const PAGE1_BULK32: VolBlock<u32, <<U160 as Mul<U128>>::Output as Div<U2>>::Output> =
|
||||||
|
unsafe { VolBlock::new(VRAM_BASE_USIZE + 0xA000) };
|
||||||
|
|
||||||
/// Reads the pixel at the given (col,row).
|
/// Reads the pixel at the given (col,row).
|
||||||
///
|
///
|
||||||
|
@ -255,9 +289,13 @@ impl Mode5 {
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds.
|
||||||
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<Color> {
|
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<Color> {
|
||||||
if page1 {
|
if page1 {
|
||||||
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
Self::PAGE1_BLOCK
|
||||||
|
.get(col + row * Self::SCREEN_WIDTH)
|
||||||
|
.map(VolAddress::read)
|
||||||
} else {
|
} else {
|
||||||
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(VolAddress::read)
|
Self::PAGE0_BLOCK
|
||||||
|
.get(col + row * Self::SCREEN_WIDTH)
|
||||||
|
.map(VolAddress::read)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,9 +306,13 @@ impl Mode5 {
|
||||||
/// Gives `None` if out of bounds.
|
/// Gives `None` if out of bounds.
|
||||||
pub fn write_pixel(page1: bool, col: usize, row: usize, color: Color) -> Option<()> {
|
pub fn write_pixel(page1: bool, col: usize, row: usize, color: Color) -> Option<()> {
|
||||||
if page1 {
|
if page1 {
|
||||||
Self::PAGE1_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
Self::PAGE1_BLOCK
|
||||||
|
.get(col + row * Self::SCREEN_WIDTH)
|
||||||
|
.map(|va| va.write(color))
|
||||||
} else {
|
} else {
|
||||||
Self::PAGE0_BLOCK.get(col + row * Self::SCREEN_WIDTH).map(|va| va.write(color))
|
Self::PAGE0_BLOCK
|
||||||
|
.get(col + row * Self::SCREEN_WIDTH)
|
||||||
|
.map(|va| va.write(color))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +320,13 @@ impl Mode5 {
|
||||||
pub fn clear_page_to(page1: bool, color: Color) {
|
pub fn clear_page_to(page1: bool, color: Color) {
|
||||||
let color32 = color.0 as u32;
|
let color32 = color.0 as u32;
|
||||||
let bulk_color = color32 << 16 | color32;
|
let bulk_color = color32 << 16 | color32;
|
||||||
for va in (if page1 { Self::PAGE1_BULK32 } else { Self::PAGE0_BULK32 }).iter() {
|
for va in (if page1 {
|
||||||
|
Self::PAGE1_BULK32
|
||||||
|
} else {
|
||||||
|
Self::PAGE0_BULK32
|
||||||
|
})
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
va.write(bulk_color)
|
va.write(bulk_color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue