mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 10:51: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
|
||||
|
||||
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 {
|
||||
unsafe {
|
||||
(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).offset(136 + 80 * 240).write_volatile(0x03E0);
|
||||
(0x600_0000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
|
||||
(0x600_0000 as *mut u16)
|
||||
.offset(120 + 80 * 240)
|
||||
.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 {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,5 +4,5 @@ merge_imports = true
|
|||
reorder_imports = true
|
||||
use_try_shorthand = true
|
||||
tab_spaces = 2
|
||||
max_width = 150
|
||||
max_width = 100
|
||||
color = "Never"
|
||||
|
|
|
@ -122,7 +122,7 @@ impl DisplayStatusSetting {
|
|||
/// 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
|
||||
/// 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.
|
||||
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
|
||||
/// to work with) then call `read_key_input()` rather than reading this register
|
||||
/// 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.
|
||||
#[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) };
|
||||
|
||||
/// 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! {
|
||||
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) };
|
||||
|
||||
/// 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! {
|
||||
NonWaveVolumeEnableSetting, u16
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
//! do, it's a giant bag of Undefined Behavior.
|
||||
|
||||
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.
|
||||
///
|
||||
|
|
|
@ -157,7 +157,11 @@ pub fn read_obj_attributes(slot: usize) -> Option<ObjectAttributes> {
|
|||
let attr0 = va_u16.cast::<OBJAttr0>().read();
|
||||
let attr1 = va_u16.offset(1).cast::<OBJAttr1>().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) };
|
||||
|
||||
/// 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! {
|
||||
/// 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,
|
||||
/// 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
|
||||
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).
|
||||
///
|
||||
|
@ -41,7 +43,9 @@ impl Mode3 {
|
|||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
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).
|
||||
|
@ -50,7 +54,9 @@ impl Mode3 {
|
|||
///
|
||||
/// Gives `None` if out of bounds.
|
||||
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.
|
||||
|
@ -68,7 +74,13 @@ impl Mode3 {
|
|||
|
||||
let color32 = color.0 as u32;
|
||||
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;
|
||||
|
||||
// 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?
|
||||
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?
|
||||
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?
|
||||
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
|
||||
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
|
||||
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).
|
||||
///
|
||||
|
@ -127,9 +145,13 @@ impl Mode4 {
|
|||
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<u8> {
|
||||
// Note(Lokathor): byte _reads_ from VRAM are okay.
|
||||
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 {
|
||||
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`
|
||||
/// values, allowing you to write two palette entries side by side as a single
|
||||
/// 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 {
|
||||
let wide_index = wide_col + row * Self::SCREEN_WIDTH / 2;
|
||||
let address: VolAddress<u16> = if page1 {
|
||||
|
@ -189,7 +213,13 @@ impl Mode4 {
|
|||
pub fn clear_page_to(page1: bool, pal8bpp: u8) {
|
||||
let pal8bpp_32 = pal8bpp as u32;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -237,16 +267,20 @@ impl Mode5 {
|
|||
const SCREEN_U32_COUNT: usize = Self::SCREEN_PIXEL_COUNT / 2;
|
||||
|
||||
// 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?
|
||||
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
|
||||
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
|
||||
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).
|
||||
///
|
||||
|
@ -255,9 +289,13 @@ impl Mode5 {
|
|||
/// Gives `None` if out of bounds.
|
||||
pub fn read_pixel(page1: bool, col: usize, row: usize) -> Option<Color> {
|
||||
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 {
|
||||
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.
|
||||
pub fn write_pixel(page1: bool, col: usize, row: usize, color: Color) -> Option<()> {
|
||||
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 {
|
||||
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) {
|
||||
let color32 = color.0 as u32;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue