#![warn(missing_docs)]
mod affine_background;
mod infinite_scrolled_map;
mod registers;
mod regular_background;
mod screenblock;
mod tiles;
mod vram_manager;
pub use affine_background::{
AffineBackground, AffineBackgroundSize, AffineBackgroundWrapBehaviour, AffineMatrixBackground,
};
use alloc::rc::Rc;
pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus};
pub use regular_background::{RegularBackground, RegularBackgroundSize};
use tiles::Tiles;
pub use vram_manager::{
DynamicTile16, DynamicTile256, TileFormat, TileSet, VRAM_MANAGER, VRamManager,
};
pub(crate) use vram_manager::TileIndex;
pub(crate) use registers::*;
use bilge::prelude::*;
use crate::{
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd, impl_zst_allocator},
display::tiled::screenblock::Screenblock,
dma::DmaControllable,
fixnum::{Num, Vector2D},
memory_mapped::MemoryMapped,
};
use super::DISPLAY_CONTROL;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct BackgroundId(pub(crate) u8);
impl From<RegularBackgroundId> for BackgroundId {
fn from(value: RegularBackgroundId) -> Self {
Self(value.0)
}
}
impl From<AffineBackgroundId> for BackgroundId {
fn from(value: AffineBackgroundId) -> Self {
Self(value.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct RegularBackgroundId(pub(crate) u8);
impl RegularBackgroundId {
#[must_use]
pub fn x_scroll_dma(self) -> DmaControllable<u16> {
unsafe { DmaControllable::new((0x0400_0010 + self.0 as usize * 4) as *mut _) }
}
#[must_use]
pub fn y_scroll_dma(self) -> DmaControllable<u16> {
unsafe { DmaControllable::new((0x0400_0012 + self.0 as usize * 4) as *mut _) }
}
#[must_use]
pub fn scroll_dma(self) -> DmaControllable<Vector2D<u16>> {
unsafe { DmaControllable::new((0x0400_0010 + self.0 as usize * 4) as *mut _) }
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct AffineBackgroundId(pub(crate) u8);
impl AffineBackgroundId {
#[must_use]
pub fn transform_dma(self) -> DmaControllable<AffineMatrixBackground> {
unsafe { DmaControllable::new((0x0400_0020 + (self.0 as usize - 2) * 16) as *mut _) }
}
}
const TRANSPARENT_TILE_INDEX: u16 = 0xffff;
#[derive(Clone, Copy, Debug, Default)]
#[repr(align(4))]
pub struct TileSetting {
tile_id: u16,
tile_effect: TileEffect,
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct TileEffect(u16);
impl TileSetting {
pub const BLANK: Self =
TileSetting::new(TRANSPARENT_TILE_INDEX, TileEffect::new(false, false, 0));
#[must_use]
pub const fn new(tile_id: u16, tile_effect: TileEffect) -> Self {
Self {
tile_id,
tile_effect,
}
}
pub const fn tile_effect(&mut self) -> &mut TileEffect {
&mut self.tile_effect
}
#[must_use]
pub const fn hflip(mut self, should_flip: bool) -> Self {
self.tile_effect = self.tile_effect.hflip(should_flip);
self
}
#[must_use]
pub const fn vflip(mut self, should_flip: bool) -> Self {
self.tile_effect = self.tile_effect.vflip(should_flip);
self
}
#[must_use]
pub const fn palette(mut self, palette_id: u8) -> Self {
self.tile_effect = self.tile_effect.palette(palette_id);
self
}
#[must_use]
pub const fn tile_id(self) -> u16 {
self.tile_id
}
const fn setting(self) -> u16 {
self.tile_effect.0
}
}
impl TileEffect {
#[must_use]
pub const fn new(hflip: bool, vflip: bool, palette_id: u8) -> Self {
Self(((hflip as u16) << 10) | ((vflip as u16) << 11) | ((palette_id as u16) << 12))
}
#[must_use]
pub const fn hflip(mut self, should_flip: bool) -> Self {
self.0 ^= (should_flip as u16) << 10;
self
}
#[must_use]
pub const fn vflip(mut self, should_flip: bool) -> Self {
self.0 ^= (should_flip as u16) << 11;
self
}
#[must_use]
pub const fn palette(mut self, palette_id: u8) -> Self {
self.0 &= 0x0fff;
self.0 |= (palette_id as u16) << 12;
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(transparent)]
pub(crate) struct Tile(u16);
impl Tile {
fn new(idx: TileIndex, setting: TileSetting) -> Self {
Self(idx.raw_index() | setting.setting())
}
fn tile_index(self, format: TileFormat) -> TileIndex {
TileIndex::new(self.0 as usize & ((1 << 10) - 1), format)
}
}
struct ScreenblockAllocator;
pub(crate) const VRAM_START: usize = 0x0600_0000;
pub(crate) const SCREENBLOCK_SIZE: usize = 0x800;
pub(crate) const CHARBLOCK_SIZE: usize = SCREENBLOCK_SIZE * 8;
const SCREENBLOCK_ALLOC_START: usize = VRAM_START + CHARBLOCK_SIZE * 2;
static SCREENBLOCK_ALLOCATOR: BlockAllocator = unsafe {
BlockAllocator::new(StartEnd {
start: || SCREENBLOCK_ALLOC_START,
end: || SCREENBLOCK_ALLOC_START + 0x4000,
})
};
impl_zst_allocator!(ScreenblockAllocator, SCREENBLOCK_ALLOCATOR);
struct RegularBackgroundCommitData {
tiles: Tiles<Tile>,
screenblock: Rc<Screenblock<RegularBackgroundSize>>,
}
#[derive(Default)]
struct RegularBackgroundData {
bg_ctrl: BackgroundControlRegister,
scroll_offset: Vector2D<u16>,
commit_data: Option<RegularBackgroundCommitData>,
}
struct AffineBackgroundCommitData {
tiles: Tiles<u8>,
screenblock: Rc<Screenblock<AffineBackgroundSize>>,
}
#[derive(Default)]
struct AffineBackgroundData {
bg_ctrl: BackgroundControlRegister,
scroll_offset: Vector2D<Num<i32, 8>>,
affine_transform: AffineMatrixBackground,
commit_data: Option<AffineBackgroundCommitData>,
}
#[derive(Default)]
pub(crate) struct BackgroundFrame {
num_regular: usize,
regular_backgrounds: [RegularBackgroundData; 4],
num_affine: usize,
affine_backgrounds: [AffineBackgroundData; 2],
}
impl BackgroundFrame {
fn set_next_regular(&mut self, data: RegularBackgroundData) -> RegularBackgroundId {
let bg_index = self.next_regular_index();
self.regular_backgrounds[bg_index] = data;
RegularBackgroundId(bg_index as u8)
}
fn next_regular_index(&mut self) -> usize {
if self.num_regular + self.num_affine * 2 >= 4 {
panic!(
"Can only have 4 backgrounds at once, affine counts as 2. regular: {}, affine: {}",
self.num_regular, self.num_affine
);
}
let index = self.num_regular;
self.num_regular += 1;
index
}
fn set_next_affine(&mut self, data: AffineBackgroundData) -> AffineBackgroundId {
let bg_index = self.next_affine_index();
self.affine_backgrounds[bg_index - 2] = data;
AffineBackgroundId(bg_index as u8)
}
fn next_affine_index(&mut self) -> usize {
if self.num_affine * 2 + self.num_regular >= 3 {
panic!(
"Can only have 4 backgrounds at once, affine counts as 2. regular: {}, affine: {}",
self.num_regular, self.num_affine
);
}
let index = self.num_affine;
self.num_affine += 1;
index + 2 }
pub fn commit(&mut self) {
let video_mode = self.num_affine as u16;
let enabled_backgrounds =
((1u16 << self.num_regular) - 1) | (((1 << self.num_affine) - 1) << 2);
let mut display_control_register = DISPLAY_CONTROL.get();
display_control_register.set_video_mode(u3::new(video_mode as u8));
display_control_register.set_enabled_backgrounds(u4::new(enabled_backgrounds as u8));
display_control_register.set_forced_blank(false);
DISPLAY_CONTROL.set(display_control_register);
for (i, regular_background) in self
.regular_backgrounds
.iter_mut()
.take(self.num_regular)
.enumerate()
{
let bg_ctrl = unsafe { MemoryMapped::new(0x0400_0008 + i * 2) };
bg_ctrl.set(regular_background.bg_ctrl);
let bg_x_offset = unsafe { MemoryMapped::new(0x0400_0010 + i * 4) };
bg_x_offset.set(regular_background.scroll_offset.x);
let bg_y_offset = unsafe { MemoryMapped::new(0x0400_0012 + i * 4) };
bg_y_offset.set(regular_background.scroll_offset.y);
if let Some(commit_data) = regular_background.commit_data.as_ref() {
unsafe {
commit_data.screenblock.copy_tiles(&commit_data.tiles);
}
commit_data.tiles.clean(commit_data.screenblock.ptr());
}
}
for (i, affine_background) in self
.affine_backgrounds
.iter_mut()
.take(self.num_affine)
.enumerate()
{
let i = i + 2;
let bg_ctrl = unsafe { MemoryMapped::new(0x0400_0008 + i * 2) };
bg_ctrl.set(affine_background.bg_ctrl);
let bg_x_offset = unsafe { MemoryMapped::new(0x0400_0028 + (i - 2) * 16) };
bg_x_offset.set(affine_background.scroll_offset.x.to_raw());
let bg_y_offset = unsafe { MemoryMapped::new(0x0400_002c + (i - 2) * 16) };
bg_y_offset.set(affine_background.scroll_offset.y.to_raw());
let affine_transform_offset = unsafe { MemoryMapped::new(0x0400_0020 + (i - 2) * 16) };
affine_transform_offset.set(affine_background.affine_transform);
if let Some(commit_data) = affine_background.commit_data.as_ref() {
unsafe {
commit_data.screenblock.copy_tiles(&commit_data.tiles);
}
commit_data.tiles.clean(commit_data.screenblock.ptr());
}
}
}
}