io registers lesson, +cargo fmt with lin length down to 100

This commit is contained in:
Lokathor 2019-02-11 21:59:17 -07:00
parent 1094794ef8
commit b67bdc80a1
14 changed files with 325 additions and 35 deletions

View file

@ -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

View file

@ -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 {}
}
}

View file

@ -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"

View file

@ -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;

View file

@ -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)]

View file

@ -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

View file

@ -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.
///

View file

@ -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,
}
})
}

View file

@ -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.

View file

@ -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)
}
}