Merge pull request #225 from gwilymk/allow-setting-size-of-backgrounds

Allow setting size of backgrounds
This commit is contained in:
Gwilym Kuiper 2022-05-04 21:00:07 +01:00 committed by GitHub
commit 5c76961506
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 244 additions and 39 deletions

View file

@ -3,7 +3,7 @@
use agb::{
display::{
tiled::{TileFormat, TileSet, TileSetting},
tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting},
Priority,
},
include_gfx,
@ -20,7 +20,7 @@ fn main(mut gba: agb::Gba) -> ! {
vram.set_background_palettes(water_tiles::water_tiles.palettes);
let mut bg = gfx.background(Priority::P0);
let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32);
for y in 0..20u16 {
for x in 0..30u16 {

View file

@ -6,6 +6,7 @@ use agb::{
display::{
object::{Object, ObjectController, Size, Sprite},
palette16::Palette16,
tiled::RegularBackgroundSize,
HEIGHT, WIDTH,
},
input::Button,
@ -54,7 +55,10 @@ fn main(mut gba: agb::Gba) -> ! {
vram.set_background_palette_raw(&MAP_PALETTE);
let tileset = TileSet::new(&MAP_TILES, TileFormat::FourBpp);
let mut background = gfx.background(agb::display::Priority::P0);
let mut background = gfx.background(
agb::display::Priority::P0,
RegularBackgroundSize::Background32x32,
);
for (i, &tile) in MAP_MAP.iter().enumerate() {
let i = i as u16;

View file

@ -1,7 +1,11 @@
#![no_std]
#![no_main]
use agb::display::{palette16::Palette16, tiled::TileSetting, Priority};
use agb::display::{
palette16::Palette16,
tiled::{RegularBackgroundSize, TileSetting},
Priority,
};
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
@ -13,7 +17,7 @@ fn main(mut gba: agb::Gba) -> ! {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
])]);
let mut bg = gfx.background(Priority::P0);
let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32);
for y in 0..20u32 {
for x in 0..30u32 {

View file

@ -1,13 +1,16 @@
#![no_std]
#![no_main]
use agb::display::example_logo;
use agb::display::{example_logo, tiled::RegularBackgroundSize};
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled0();
let mut map = gfx.background(agb::display::Priority::P0);
let mut map = gfx.background(
agb::display::Priority::P0,
RegularBackgroundSize::Background32x32,
);
example_logo::display_logo(&mut map, &mut vram);

View file

@ -2,7 +2,10 @@
#![no_main]
use agb::{
display::{tiled::TileSetting, Font, Priority},
display::{
tiled::{RegularBackgroundSize, TileSetting},
Font, Priority,
},
include_font,
};
@ -22,7 +25,7 @@ fn main(mut gba: agb::Gba) -> ! {
let background_tile = vram.new_dynamic_tile().fill_with(0);
let mut bg = gfx.background(Priority::P0);
let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32);
for y in 0..20u16 {
for x in 0..30u16 {

View file

@ -4,7 +4,7 @@
use core::cell::RefCell;
use agb::{
display::example_logo,
display::{example_logo, tiled::RegularBackgroundSize},
fixnum::FixedNum,
interrupt::{free, Interrupt},
};
@ -19,7 +19,10 @@ struct BackCosines {
fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled0();
let mut background = gfx.background(agb::display::Priority::P0);
let mut background = gfx.background(
agb::display::Priority::P0,
RegularBackgroundSize::Background32x32,
);
example_logo::display_logo(&mut background, &mut vram);

View file

@ -23,13 +23,15 @@ pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
}
#[cfg(test)]
mod tests {
use crate::display::{tiled::RegularBackgroundSize, Priority};
use super::*;
#[test_case]
fn logo_display(gba: &mut crate::Gba) {
let (gfx, mut vram) = gba.display.video.tiled0();
let mut map = gfx.background(crate::display::Priority::P0);
let mut map = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32);
display_logo(&mut map, &mut vram);

View file

@ -213,7 +213,10 @@ mod tests {
fn font_display(gba: &mut crate::Gba) {
let (gfx, mut vram) = gba.display.video.tiled0();
let mut bg = gfx.background(crate::display::Priority::P0);
let mut bg = gfx.background(
crate::display::Priority::P0,
crate::display::tiled::RegularBackgroundSize::Background32x32,
);
vram.set_background_palette_raw(&[
0x0000, 0x0ff0, 0x00ff, 0xf00f, 0xf0f0, 0x0f0f, 0xaaaa, 0x5555, 0x0000, 0x0000, 0x0000,

View file

@ -63,8 +63,8 @@ impl<'a> InfiniteScrolledMap<'a> {
let offset = self.current_pos - (x_start * 8, y_start * 8).into();
let offset_scroll = (
offset.x.rem_euclid(32 * 8) as u16,
offset.y.rem_euclid(32 * 8) as u16,
self.map.size().tile_pos_x(offset.x),
self.map.size().tile_pos_y(offset.y),
)
.into();
@ -120,6 +120,8 @@ impl<'a> InfiniteScrolledMap<'a> {
let difference_tile_x = div_ceil(difference.x, 8);
let difference_tile_y = div_ceil(difference.y, 8);
let size = self.map.size();
let vertical_rect_to_update: Rect<i32> = if div_floor(old_pos.x, 8) != new_tile_x {
// need to update the x line
// calculate which direction we need to update
@ -177,8 +179,8 @@ impl<'a> InfiniteScrolledMap<'a> {
self.map.set_tile(
vram,
(
(tile_x - self.offset.x).rem_euclid(32) as u16,
(tile_y - self.offset.y).rem_euclid(32) as u16,
size.tile_pos_x(tile_x - self.offset.x),
size.tile_pos_y(tile_y - self.offset.y),
)
.into(),
tileset,
@ -188,8 +190,8 @@ impl<'a> InfiniteScrolledMap<'a> {
let current_scroll = self.map.scroll_pos();
let new_scroll = (
(current_scroll.x as i32 + difference.x).rem_euclid(32 * 8) as u16,
(current_scroll.y as i32 + difference.y).rem_euclid(32 * 8) as u16,
size.px_offset_x(current_scroll.x as i32 + difference.x),
size.px_offset_y(current_scroll.y as i32 + difference.y),
)
.into();

View file

@ -7,7 +7,9 @@ use crate::dma::dma_copy16;
use crate::fixnum::Vector2D;
use crate::memory_mapped::MemoryMapped;
use super::{Tile, TileSet, TileSetting, VRamManager};
use super::{RegularBackgroundSize, Tile, TileSet, TileSetting, VRamManager};
use alloc::{vec, vec::Vec};
pub struct RegularMap {
background_id: u8,
@ -17,14 +19,21 @@ pub struct RegularMap {
y_scroll: u16,
priority: Priority,
tiles: [Tile; 32 * 32],
tiles: Vec<Tile>,
tiles_dirty: bool,
size: RegularBackgroundSize,
}
pub const TRANSPARENT_TILE_INDEX: u16 = (1 << 10) - 1;
impl RegularMap {
pub(crate) fn new(background_id: u8, screenblock: u8, priority: Priority) -> Self {
pub(crate) fn new(
background_id: u8,
screenblock: u8,
priority: Priority,
size: RegularBackgroundSize,
) -> Self {
Self {
background_id,
@ -33,8 +42,10 @@ impl RegularMap {
y_scroll: 0,
priority,
tiles: [Tile::default(); 32 * 32],
tiles: vec![Default::default(); size.num_tiles()],
tiles_dirty: true,
size,
}
}
@ -45,7 +56,7 @@ impl RegularMap {
tileset: &TileSet<'_>,
tile_setting: TileSetting,
) {
let pos = (pos.x + pos.y * 32) as usize;
let pos = self.size.gba_offset(pos);
let old_tile = self.tiles[pos];
if old_tile != Tile::default() {
@ -93,7 +104,9 @@ impl RegularMap {
}
pub fn commit(&mut self, vram: &mut VRamManager) {
let new_bg_control_value = (self.priority as u16) | ((self.screenblock as u16) << 8);
let new_bg_control_value = (self.priority as u16)
| ((self.screenblock as u16) << 8)
| (self.size.size_flag() << 14);
self.bg_control_register().set(new_bg_control_value);
self.bg_h_offset().set(self.x_scroll);
@ -111,7 +124,7 @@ impl RegularMap {
dma_copy16(
self.tiles.as_ptr() as *const u16,
screenblock_memory,
32 * 32,
self.size.num_tiles(),
);
}
@ -127,6 +140,10 @@ impl RegularMap {
(self.x_scroll, self.y_scroll).into()
}
pub(crate) fn size(&self) -> RegularBackgroundSize {
self.size
}
const fn bg_control_register(&self) -> MemoryMapped<u16> {
unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id as usize) }
}
@ -147,7 +164,10 @@ impl RegularMap {
pub struct MapLoan<'a, T> {
map: T,
background_id: u8,
screenblock_id: u8,
screenblock_length: u8,
regular_map_list: &'a RefCell<Bitarray<1>>,
screenblock_list: &'a RefCell<Bitarray<1>>,
}
impl<'a, T> Deref for MapLoan<'a, T> {
@ -168,12 +188,18 @@ impl<'a, T> MapLoan<'a, T> {
pub(crate) fn new(
map: T,
background_id: u8,
screenblock_id: u8,
screenblock_length: u8,
regular_map_list: &'a RefCell<Bitarray<1>>,
screenblock_list: &'a RefCell<Bitarray<1>>,
) -> Self {
MapLoan {
map,
background_id,
screenblock_id,
screenblock_length,
regular_map_list,
screenblock_list,
}
}
}
@ -183,5 +209,11 @@ impl<'a, T> Drop for MapLoan<'a, T> {
self.regular_map_list
.borrow_mut()
.set(self.background_id as usize, false);
let mut screenblock_list = self.screenblock_list.borrow_mut();
for i in self.screenblock_id..self.screenblock_id + self.screenblock_length {
screenblock_list.set(i as usize, false);
}
}
}

View file

@ -3,11 +3,86 @@ mod map;
mod tiled0;
mod vram_manager;
use agb_fixnum::Vector2D;
pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus};
pub use map::{MapLoan, RegularMap};
pub use tiled0::Tiled0;
pub use vram_manager::{DynamicTile, TileFormat, TileIndex, TileSet, VRamManager};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RegularBackgroundSize {
Background32x32,
Background64x32,
Background32x64,
Background64x64,
}
impl RegularBackgroundSize {
pub fn width(&self) -> u32 {
match self {
RegularBackgroundSize::Background32x32 => 32,
RegularBackgroundSize::Background64x32 => 64,
RegularBackgroundSize::Background32x64 => 32,
RegularBackgroundSize::Background64x64 => 64,
}
}
pub fn height(&self) -> u32 {
match self {
RegularBackgroundSize::Background32x32 => 32,
RegularBackgroundSize::Background64x32 => 32,
RegularBackgroundSize::Background32x64 => 64,
RegularBackgroundSize::Background64x64 => 64,
}
}
pub(crate) fn size_flag(&self) -> u16 {
match self {
RegularBackgroundSize::Background32x32 => 0,
RegularBackgroundSize::Background64x32 => 1,
RegularBackgroundSize::Background32x64 => 2,
RegularBackgroundSize::Background64x64 => 3,
}
}
pub(crate) fn num_tiles(&self) -> usize {
(self.width() * self.height()) as usize
}
pub(crate) fn num_screen_blocks(&self) -> usize {
self.num_tiles() / (32 * 32)
}
// This is hilariously complicated due to how the GBA stores the background screenblocks.
// See https://www.coranac.com/tonc/text/regbg.htm#sec-map for an explanation
pub(crate) fn gba_offset(&self, pos: Vector2D<u16>) -> usize {
let x_mod = pos.x & (self.width() as u16 - 1);
let y_mod = pos.y & (self.height() as u16 - 1);
let screenblock = (x_mod / 32) + (y_mod / 32) * (self.width() as u16 / 32);
let pos = screenblock * 32 * 32 + (x_mod % 32 + 32 * (y_mod % 32));
pos as usize
}
pub(crate) fn tile_pos_x(&self, x: i32) -> u16 {
((x as u32) & (self.width() - 1)) as u16
}
pub(crate) fn tile_pos_y(&self, y: i32) -> u16 {
((y as u32) & (self.height() - 1)) as u16
}
pub(crate) fn px_offset_x(&self, x: i32) -> u16 {
((x as u32) & (self.width() * 8 - 1)) as u16
}
pub(crate) fn px_offset_y(&self, y: i32) -> u16 {
((y as u32) & (self.height() * 8 - 1)) as u16
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(transparent)]
struct Tile(u16);
@ -47,3 +122,31 @@ impl TileSetting {
self.0 & !((1 << 10) - 1)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test_case]
fn rem_euclid_width_works(_gba: &mut crate::Gba) {
use RegularBackgroundSize::*;
let sizes = [
Background32x32,
Background32x64,
Background64x32,
Background64x64,
];
for size in sizes.iter() {
let width = size.width() as i32;
assert_eq!(size.tile_pos_x(8), 8);
assert_eq!(size.tile_pos_x(3 + width), 3);
assert_eq!(size.tile_pos_x(7 + width * 9), 7);
assert_eq!(size.tile_pos_x(-8), (size.width() - 8) as u16);
assert_eq!(size.tile_pos_x(-17 - width * 8), (size.width() - 17) as u16);
}
}
}

View file

@ -5,10 +5,11 @@ use crate::{
display::{set_graphics_mode, DisplayMode, Priority},
};
use super::{MapLoan, RegularMap};
use super::{MapLoan, RegularBackgroundSize, RegularMap};
pub struct Tiled0 {
regular: RefCell<Bitarray<1>>,
screenblocks: RefCell<Bitarray<1>>,
}
impl Tiled0 {
@ -17,20 +18,61 @@ impl Tiled0 {
Self {
regular: Default::default(),
screenblocks: Default::default(),
}
}
pub fn background(&self, priority: Priority) -> MapLoan<'_, RegularMap> {
pub fn background(
&self,
priority: Priority,
size: RegularBackgroundSize,
) -> MapLoan<'_, RegularMap> {
let mut regular = self.regular.borrow_mut();
let new_background = regular.first_zero().unwrap();
if new_background >= 4 {
panic!("can only have 4 active backgrounds");
}
let bg = RegularMap::new(new_background as u8, (new_background + 16) as u8, priority);
let num_screenblocks = size.num_screen_blocks();
let mut screenblocks = self.screenblocks.borrow_mut();
let screenblock = find_screenblock_gap(&screenblocks, num_screenblocks);
for id in screenblock..(screenblock + num_screenblocks) {
screenblocks.set(id, true);
}
let bg = RegularMap::new(new_background as u8, screenblock as u8 + 16, priority, size);
regular.set(new_background, true);
MapLoan::new(bg, new_background as u8, &self.regular)
MapLoan::new(
bg,
new_background as u8,
screenblock as u8,
num_screenblocks as u8,
&self.regular,
&self.screenblocks,
)
}
}
fn find_screenblock_gap(screenblocks: &Bitarray<1>, gap: usize) -> usize {
let mut candidate = 0;
'outer: while candidate < 16 - gap {
let starting_point = candidate;
for attempt in starting_point..(starting_point + gap) {
if screenblocks.get(attempt) == Some(true) {
candidate = attempt + 1;
continue 'outer;
}
}
return candidate;
}
panic!(
"Failed to find screenblock gap of at least {} elements",
gap
);
}

View file

@ -7,7 +7,8 @@ use agb::{
display::{
object::{Graphics, Object, ObjectController, Tag, TagMap},
tiled::{
InfiniteScrolledMap, PartialUpdateStatus, TileFormat, TileSet, TileSetting, VRamManager,
InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet,
TileSetting, VRamManager,
},
Priority, HEIGHT, WIDTH,
},
@ -777,8 +778,8 @@ impl<'a, 'b, 'c> PlayingLevel<'a, 'b> {
fn main(mut agb: agb::Gba) -> ! {
let (tiled, mut vram) = agb.display.video.tiled0();
vram.set_background_palettes(tile_sheet::background.palettes);
let mut splash_screen = tiled.background(Priority::P0);
let mut world_display = tiled.background(Priority::P0);
let mut splash_screen = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
let mut world_display = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
let tileset = TileSet::new(tile_sheet::background.tiles, TileFormat::FourBpp);
@ -845,7 +846,7 @@ fn main(mut agb: agb::Gba) -> ! {
let map_current_level = current_level;
let mut background = InfiniteScrolledMap::new(
tiled.background(Priority::P2),
tiled.background(Priority::P2, RegularBackgroundSize::Background32x64),
Box::new(|pos: Vector2D<i32>| {
let level = &map_tiles::LEVELS[map_current_level as usize];
(
@ -860,7 +861,7 @@ fn main(mut agb: agb::Gba) -> ! {
}),
);
let mut foreground = InfiniteScrolledMap::new(
tiled.background(Priority::P0),
tiled.background(Priority::P0, RegularBackgroundSize::Background64x32),
Box::new(|pos: Vector2D<i32>| {
let level = &map_tiles::LEVELS[map_current_level as usize];
(

View file

@ -12,7 +12,10 @@ use alloc::{boxed::Box, vec::Vec};
use agb::{
display::{
object::{Graphics, Object, ObjectController, Sprite, Tag, TagMap},
tiled::{InfiniteScrolledMap, TileFormat, TileSet, TileSetting, VRamManager},
tiled::{
InfiniteScrolledMap, RegularBackgroundSize, TileFormat, TileSet, TileSetting,
VRamManager,
},
Priority, HEIGHT, WIDTH,
},
fixnum::{FixedNum, Rect, Vector2D},
@ -2221,7 +2224,7 @@ fn game_with_level(gba: &mut agb::Gba) {
let object = gba.display.object.get();
let backdrop = InfiniteScrolledMap::new(
background.background(Priority::P2),
background.background(Priority::P2, RegularBackgroundSize::Background32x32),
Box::new(|pos| {
(
&tileset,
@ -2235,7 +2238,7 @@ fn game_with_level(gba: &mut agb::Gba) {
);
let foreground = InfiniteScrolledMap::new(
background.background(Priority::P0),
background.background(Priority::P0, RegularBackgroundSize::Background32x32),
Box::new(|pos| {
(
&tileset,
@ -2249,7 +2252,7 @@ fn game_with_level(gba: &mut agb::Gba) {
);
let clouds = InfiniteScrolledMap::new(
background.background(Priority::P3),
background.background(Priority::P3, RegularBackgroundSize::Background32x32),
Box::new(|pos| {
(
&tileset,