Merge pull request #283 from agbrs/windows

Windows
This commit is contained in:
Corwin 2022-08-06 14:10:12 +01:00 committed by GitHub
commit 319cb7da6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 349 additions and 3 deletions

View file

@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Custom allocator support using the `Allocator` trait for `HashMap`. This means the `HashMap` can be used with `InternalAllocator` to allocate to IWRAM or the `ExternalAllocator` to explicitly allocate to EWRAM.
- Support for using windows on the GBA. Windows are used to selectively enable rendering of certain layers or effects.
## [0.11.1] - 2022/08/02

View file

@ -121,6 +121,7 @@ macro_rules! fixed_width_signed_integer_impl {
};
}
fixed_width_unsigned_integer_impl!(u8);
fixed_width_unsigned_integer_impl!(i16);
fixed_width_unsigned_integer_impl!(u16);
fixed_width_unsigned_integer_impl!(i32);

55
agb/examples/windows.rs Normal file
View file

@ -0,0 +1,55 @@
#![no_std]
#![no_main]
use agb::display::{example_logo, tiled::RegularBackgroundSize, window::WinIn};
use agb::display::{HEIGHT, WIDTH};
use agb::fixnum::{Num, Rect, Vector2D};
use agb::interrupt::VBlank;
type FNum = Num<i32, 8>;
#[agb::entry]
fn entry(mut gba: agb::Gba) -> ! {
main(gba)
}
fn main(mut gba: agb::Gba) -> ! {
let (gfx, mut vram) = gba.display.video.tiled0();
let mut map = gfx.background(
agb::display::Priority::P0,
RegularBackgroundSize::Background32x32,
);
let mut window = gba.display.window.get();
window
.win_in(WinIn::Win0)
.set_background_enable(map.background(), true)
.set_position(&Rect::new((10, 10).into(), (64, 64).into()))
.enable();
example_logo::display_logo(&mut map, &mut vram);
let mut pos: Vector2D<FNum> = (10, 10).into();
let mut velocity: Vector2D<FNum> = Vector2D::new(1.into(), 1.into());
let vblank = VBlank::get();
loop {
pos += velocity;
if pos.x.floor() > WIDTH - 64 || pos.x.floor() < 0 {
velocity.x *= -1;
}
if pos.y.floor() > HEIGHT - 64 || pos.y.floor() < 0 {
velocity.y *= -1;
}
window
.win_in(WinIn::Win0)
.set_position(&Rect::new(pos.floor(), (64, 64).into()));
vblank.wait_for_vblank();
window.commit();
}
}

View file

@ -4,7 +4,7 @@ use bitflags::bitflags;
use modular_bitfield::BitfieldSpecifier;
use video::Video;
use self::object::ObjectController;
use self::{object::ObjectController, window::Windows};
/// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer.
pub mod bitmap3;
@ -23,6 +23,8 @@ pub mod tiled;
/// Giving out graphics mode.
pub mod video;
pub mod window;
mod font;
pub use font::{Font, FontLetter};
@ -67,6 +69,7 @@ enum DisplayMode {
pub struct Display {
pub video: Video,
pub object: ObjectDistribution,
pub window: WindowDist,
}
#[non_exhaustive]
@ -78,11 +81,21 @@ impl ObjectDistribution {
}
}
#[non_exhaustive]
pub struct WindowDist {}
impl WindowDist {
pub fn get(&mut self) -> Windows {
Windows::new()
}
}
impl Display {
pub(crate) const unsafe fn new() -> Self {
Display {
video: Video {},
object: ObjectDistribution {},
window: WindowDist {},
}
}
}

View file

@ -1,6 +1,6 @@
use alloc::boxed::Box;
use super::{MapLoan, RegularMap, TileSet, TileSetting, VRamManager};
use super::{BackgroundID, MapLoan, RegularMap, TileSet, TileSetting, VRamManager};
use crate::{
display,
@ -414,6 +414,11 @@ impl<'a> InfiniteScrolledMap<'a> {
pub fn clear(&mut self, vram: &mut VRamManager) {
self.map.clear(vram);
}
#[must_use]
pub const fn background(&self) -> BackgroundID {
self.map.background()
}
}
fn div_floor(x: i32, y: i32) -> i32 {

View file

@ -7,7 +7,7 @@ use crate::dma::dma_copy16;
use crate::fixnum::Vector2D;
use crate::memory_mapped::MemoryMapped;
use super::{RegularBackgroundSize, Tile, TileSet, TileSetting, VRamManager};
use super::{BackgroundID, RegularBackgroundSize, Tile, TileSet, TileSetting, VRamManager};
use alloc::{vec, vec::Vec};
@ -201,6 +201,11 @@ impl<'a, T> MapLoan<'a, T> {
screenblock_list,
}
}
#[must_use]
pub const fn background(&self) -> BackgroundID {
BackgroundID(self.background_id)
}
}
impl<'a, T> Drop for MapLoan<'a, T> {

View file

@ -17,6 +17,9 @@ pub enum RegularBackgroundSize {
Background64x64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BackgroundID(pub(crate) u8);
impl RegularBackgroundSize {
#[must_use]
pub fn width(&self) -> u32 {

263
agb/src/display/window.rs Normal file
View file

@ -0,0 +1,263 @@
#![deny(missing_docs)]
//! The window feature of the GBA.
use crate::{fixnum::Rect, memory_mapped::MemoryMapped};
use super::{tiled::BackgroundID, DISPLAY_CONTROL, HEIGHT, WIDTH};
/// The windows feature of the Game Boy Advance can selectively display
/// backgrounds or objects on the screen and can selectively enable and disable
/// effects. This gives out references and holds changes before they can be committed.
pub struct Windows {
wins: [MovableWindow; 2],
out: Window,
obj: Window,
}
const REG_HORIZONTAL_BASE: *mut u16 = 0x0400_0040 as *mut _;
const REG_VERTICAL_BASE: *mut u16 = 0x0400_0044 as *mut _;
const REG_WINDOW_CONTROL_BASE: *mut u16 = 0x0400_0048 as *mut _;
/// The two Windows that have an effect inside of them
pub enum WinIn {
/// The higher priority window
Win0,
/// The lower priority window
Win1,
}
impl Windows {
pub(crate) fn new() -> Self {
let s = Self {
wins: [MovableWindow::new(), MovableWindow::new()],
out: Window::new(),
obj: Window::new(),
};
s.commit();
s
}
/// Returns a reference to the window that is used when outside all other windows
#[inline(always)]
pub fn win_out(&mut self) -> &mut Window {
&mut self.out
}
/// Gives a reference to the specified window that has effect when inside of it's boundary
#[inline(always)]
pub fn win_in(&mut self, id: WinIn) -> &mut MovableWindow {
&mut self.wins[id as usize]
}
/// Gives a reference to the window that is controlled by sprites and objects
#[inline(always)]
pub fn win_obj(&mut self) -> &mut Window {
&mut self.obj
}
/// Commits the state of the windows as dictated by the various functions to
/// modify them. This should be done during vblank shortly after the wait
/// for next vblank call.
pub fn commit(&self) {
for (id, win) in self.wins.iter().enumerate() {
win.commit(id);
}
self.out.commit(2);
self.obj.commit(3);
let enabled_bits = ((self.obj.is_enabled() as u16) << 2)
| ((self.wins[1].is_enabled() as u16) << 1)
| (self.wins[0].is_enabled() as u16);
DISPLAY_CONTROL.set_bits(enabled_bits, 3, 0xD);
}
}
/// A non movable window
pub struct Window {
window_bits: u8,
}
/// A window that can be moved
pub struct MovableWindow {
inner: Window,
rect: Rect<u8>,
}
impl Window {
fn new() -> Window {
Self { window_bits: 0 }
}
/// Enables the window, must call [Windows::commit] for this change to be
/// seen. If a window is not enabled it will not have an effect on the
/// display.
#[inline(always)]
pub fn enable(&mut self) -> &mut Self {
self.set_bit(7, true);
self
}
/// Disables the window, must call [Windows::commit] for this change to be
/// seen.
#[inline(always)]
pub fn disable(&mut self) -> &mut Self {
self.set_bit(7, false);
self
}
fn is_enabled(&self) -> bool {
(self.window_bits >> 7) != 0
}
fn set_bit(&mut self, bit: usize, value: bool) {
self.window_bits &= u8::MAX ^ (1 << bit);
self.window_bits |= (value as u8) << bit;
}
/// Resets the window to it's default state, must call [Windows::commit] for
/// this change to be seen. The default state is the window disabled with
/// nothing rendered.
#[inline(always)]
pub fn reset(&mut self) -> &mut Self {
*self = Self::new();
self
}
/// Sets whether the blend is enabled inside of this window, must call
/// [Windows::commit] for this change to be seen.
#[inline(always)]
pub fn set_blend_enable(&mut self, blnd: bool) -> &mut Self {
self.set_bit(5, blnd);
self
}
/// Sets whether the given background will be rendered inside this window,
/// must call [Windows::commit] for this change to be seen.
#[inline(always)]
pub fn set_background_enable(&mut self, back: BackgroundID, enable: bool) -> &mut Self {
self.set_bit(back.0 as usize, enable);
self
}
/// Sets whether objects will be rendered inside this window, must call
/// [Windows::commit] for this change to be seen.
#[inline(always)]
pub fn set_object_enable(&mut self, obj: bool) -> &mut Self {
self.set_bit(4, obj);
self
}
fn commit(&self, id: usize) {
let base_reg = id / 2;
let offset_in_reg = (id % 2) * 8;
unsafe {
let reg = MemoryMapped::new(REG_WINDOW_CONTROL_BASE.add(base_reg) as usize);
reg.set_bits(self.window_bits as u16, 8, offset_in_reg as u16);
}
}
}
impl MovableWindow {
fn new() -> Self {
Self {
inner: Window::new(),
rect: Rect::new((0, 0).into(), (0, 0).into()),
}
}
/// Enables the window, must call [Windows::commit] for this change to be
/// seen. If a window is not enabled it will not have an effect on the
/// display.
#[inline(always)]
pub fn enable(&mut self) -> &mut Self {
self.inner.enable();
self
}
/// Disables the window, must call [Windows::commit] for this change to be
/// seen.
#[inline(always)]
pub fn disable(&mut self) -> &mut Self {
self.inner.disable();
self
}
fn is_enabled(&self) -> bool {
self.inner.is_enabled()
}
/// Resets the window to it's default state, must call [Windows::commit] for
/// this change to be seen. The default state is the window disabled with
/// nothing rendered and represents a 0x0 rectangle at (0, 0).
#[inline(always)]
pub fn reset(&mut self) -> &mut Self {
*self = Self::new();
self
}
/// Sets whether the blend is enabled inside of this window, must call
/// [Windows::commit] for this change to be seen.
#[inline(always)]
pub fn set_blend_enable(&mut self, blnd: bool) -> &mut Self {
self.inner.set_blend_enable(blnd);
self
}
/// Sets whether the given background will be rendered inside this window,
/// must call [Windows::commit] for this change to be seen.
#[inline(always)]
pub fn set_background_enable(&mut self, back: BackgroundID, enable: bool) -> &mut Self {
self.inner.set_background_enable(back, enable);
self
}
/// Sets whether objects will be rendered inside this window, must call
/// [Windows::commit] for this change to be seen.
#[inline(always)]
pub fn set_object_enable(&mut self, obj: bool) -> &mut Self {
self.inner.set_object_enable(obj);
self
}
fn commit(&self, id: usize) {
self.inner.commit(id);
let left_right =
(self.rect.position.x as u16) << 8 | (self.rect.position.x + self.rect.size.x) as u16;
let top_bottom =
(self.rect.position.y as u16) << 8 | (self.rect.position.y + self.rect.size.y) as u16;
unsafe {
REG_HORIZONTAL_BASE.add(id).write_volatile(left_right);
REG_VERTICAL_BASE.add(id).write_volatile(top_bottom);
}
}
/// Sets the area of what is inside the window using [u8] representation,
/// which is closest to what the GBA uses. Most of the time [set_position]
/// should be used.
#[inline(always)]
pub fn set_position_u8(&mut self, rect: Rect<u8>) -> &mut Self {
self.rect = rect;
self
}
/// Sets the position of the area that is inside the window.
#[inline(always)]
pub fn set_position(&mut self, rect: &Rect<i32>) -> &mut Self {
let new_rect = Rect::new(
(
rect.position.x.clamp(0, WIDTH) as u8,
rect.position.y.clamp(0, HEIGHT) as u8,
)
.into(),
(rect.size.x as u8, rect.size.y as u8).into(),
);
self.set_position_u8(new_rect)
}
}