mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-10 00:51:34 +11:00
commit
0a991af2e9
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Added
|
### 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.
|
- 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.
|
- Support for using windows on the GBA. Windows are used to selectively enable rendering of certain layers or effects.
|
||||||
|
- Support for the blend mode of the GBA. Blending allows for alpha blending between layers and fading to black and white.
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
- Fixed the fast magnitude function in agb_fixnum. This is also used in fast_normalise. Previously only worked for positive (x, y).
|
- Fixed the fast magnitude function in agb_fixnum. This is also used in fast_normalise. Previously only worked for positive (x, y).
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
use core::{
|
use core::{
|
||||||
cmp::{Eq, Ord, PartialEq, PartialOrd},
|
cmp::{Eq, Ord, PartialEq, PartialOrd},
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
|
mem::size_of,
|
||||||
ops::{
|
ops::{
|
||||||
Add, AddAssign, BitAnd, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, Shr,
|
Add, AddAssign, BitAnd, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, Shr,
|
||||||
Sub, SubAssign,
|
Sub, SubAssign,
|
||||||
|
@ -298,6 +299,42 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Num<I, N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to perform the conversion between two integer types and between
|
||||||
|
/// two different fractional precisions
|
||||||
|
/// ```
|
||||||
|
/// # use agb_fixnum::*;
|
||||||
|
/// let a: Num<i32, 8> = 1.into();
|
||||||
|
/// let b: Option<Num<u8, 4>> = a.try_change_base();
|
||||||
|
/// assert_eq!(b, Some(1.into()));
|
||||||
|
///
|
||||||
|
/// let a: Num<i32, 8> = 18.into();
|
||||||
|
/// let b: Option<Num<u8, 4>> = a.try_change_base();
|
||||||
|
/// assert_eq!(b, None);
|
||||||
|
/// ```
|
||||||
|
pub fn try_change_base<J: FixedWidthUnsignedInteger + TryFrom<I>, const M: usize>(
|
||||||
|
self,
|
||||||
|
) -> Option<Num<J, M>> {
|
||||||
|
if size_of::<I>() > size_of::<J>() {
|
||||||
|
// I bigger than J, perform the shift in I to preserve precision
|
||||||
|
let n = if N < M {
|
||||||
|
self.0 << (M - N)
|
||||||
|
} else {
|
||||||
|
self.0 >> (N - M)
|
||||||
|
};
|
||||||
|
|
||||||
|
let n = n.try_into().ok()?;
|
||||||
|
|
||||||
|
Some(Num(n))
|
||||||
|
} else {
|
||||||
|
// J bigger than I, perform the shift in J to preserve precision
|
||||||
|
let n: J = self.0.try_into().ok()?;
|
||||||
|
|
||||||
|
let n = if N < M { n << (M - N) } else { n >> (N - M) };
|
||||||
|
|
||||||
|
Some(Num(n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A bit for bit conversion from a number to a fixed num
|
/// A bit for bit conversion from a number to a fixed num
|
||||||
pub fn from_raw(n: I) -> Self {
|
pub fn from_raw(n: I) -> Self {
|
||||||
Num(n)
|
Num(n)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
|
use agb::display::blend::{BlendMode, Layer};
|
||||||
use agb::display::{example_logo, tiled::RegularBackgroundSize, window::WinIn};
|
use agb::display::{example_logo, tiled::RegularBackgroundSize, window::WinIn};
|
||||||
use agb::display::{HEIGHT, WIDTH};
|
use agb::display::{HEIGHT, WIDTH};
|
||||||
use agb::fixnum::{Num, Rect, Vector2D};
|
use agb::fixnum::{num, Num, Rect, Vector2D};
|
||||||
use agb::interrupt::VBlank;
|
use agb::interrupt::VBlank;
|
||||||
|
|
||||||
type FNum = Num<i32, 8>;
|
type FNum = Num<i32, 8>;
|
||||||
|
@ -27,11 +28,27 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
.set_position(&Rect::new((10, 10).into(), (64, 64).into()))
|
.set_position(&Rect::new((10, 10).into(), (64, 64).into()))
|
||||||
.enable();
|
.enable();
|
||||||
|
|
||||||
|
window
|
||||||
|
.win_out()
|
||||||
|
.enable()
|
||||||
|
.set_background_enable(map.background(), true)
|
||||||
|
.set_blend_enable(true);
|
||||||
|
|
||||||
example_logo::display_logo(&mut map, &mut vram);
|
example_logo::display_logo(&mut map, &mut vram);
|
||||||
|
|
||||||
|
let mut blend = gba.display.blend.get();
|
||||||
|
|
||||||
|
blend
|
||||||
|
.set_background_enable(Layer::Top, map.background(), true)
|
||||||
|
.set_backdrop_enable(Layer::Bottom, true)
|
||||||
|
.set_blend_mode(BlendMode::Normal);
|
||||||
|
|
||||||
let mut pos: Vector2D<FNum> = (10, 10).into();
|
let mut pos: Vector2D<FNum> = (10, 10).into();
|
||||||
let mut velocity: Vector2D<FNum> = Vector2D::new(1.into(), 1.into());
|
let mut velocity: Vector2D<FNum> = Vector2D::new(1.into(), 1.into());
|
||||||
|
|
||||||
|
let mut blend_amount: Num<i32, 8> = num!(0.5);
|
||||||
|
let mut blend_velocity: Num<i32, 8> = Num::new(1) / 128;
|
||||||
|
|
||||||
let vblank = VBlank::get();
|
let vblank = VBlank::get();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -45,11 +62,21 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
velocity.y *= -1;
|
velocity.y *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blend_amount += blend_velocity;
|
||||||
|
if blend_amount > num!(0.75) || blend_amount < num!(0.25) {
|
||||||
|
blend_velocity *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blend_amount = blend_amount.clamp(0.into(), 1.into());
|
||||||
|
|
||||||
|
blend.set_blend_weight(Layer::Top, blend_amount.try_change_base().unwrap());
|
||||||
|
|
||||||
window
|
window
|
||||||
.win_in(WinIn::Win0)
|
.win_in(WinIn::Win0)
|
||||||
.set_position(&Rect::new(pos.floor(), (64, 64).into()));
|
.set_position(&Rect::new(pos.floor(), (64, 64).into()));
|
||||||
|
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
window.commit();
|
window.commit();
|
||||||
|
blend.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
216
agb/src/display/blend.rs
Normal file
216
agb/src/display/blend.rs
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
//! This controls the blending modes on the GBA.
|
||||||
|
//!
|
||||||
|
//! For now a description of how blending can be used is found on [the tonc page
|
||||||
|
//! for graphic
|
||||||
|
//! effects](https://www.coranac.com/tonc/text/gfx.htm#ssec-bld-gba). See the
|
||||||
|
//! [Blend] struct for all the functions to manage blend effects. You accquire
|
||||||
|
//! the Blend struct through the [Display][super::Display] struct.
|
||||||
|
//! ```no_run
|
||||||
|
//! # #![no_main]
|
||||||
|
//! # #![no_std]
|
||||||
|
//! # fn blend(mut gba: agb::Gba) {
|
||||||
|
//! let mut blend = gba.display.blend.get();
|
||||||
|
//! // ...
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//! where `gba` is a mutable [Gba][crate::Gba] struct.
|
||||||
|
|
||||||
|
use crate::{fixnum::Num, memory_mapped::set_bits};
|
||||||
|
|
||||||
|
use super::tiled::BackgroundID;
|
||||||
|
|
||||||
|
/// The layers, top layer will be blended into the bottom layer
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Layer {
|
||||||
|
/// Top layer gets blended into the bottom layer
|
||||||
|
Top = 0,
|
||||||
|
/// The bottom layer of the blend
|
||||||
|
Bottom = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The different blend modes avaliable on the GBA
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum BlendMode {
|
||||||
|
// No blending
|
||||||
|
Off = 0,
|
||||||
|
// Aditive blending, use the [Blend::set_blend_weight] function to use this
|
||||||
|
Normal = 0b01,
|
||||||
|
// Brighten, use the [Blend::set_fade] to use this
|
||||||
|
FadeToWhite = 0b10,
|
||||||
|
// Darken, use the [Blend::set_fade] to use this
|
||||||
|
FadeToBlack = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manages the blending, won't cause anything to change unless [Blend::commit]
|
||||||
|
/// is called.
|
||||||
|
pub struct Blend {
|
||||||
|
targets: u16,
|
||||||
|
blend_weights: u16,
|
||||||
|
fade_weight: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When making many modifications to a layer, it is convenient to operate on
|
||||||
|
/// that layer directly. This is created by the [Blend::layer] function and
|
||||||
|
/// operates on that layer.
|
||||||
|
pub struct BlendLayer<'blend> {
|
||||||
|
blend: &'blend mut Blend,
|
||||||
|
layer: Layer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlendLayer<'_> {
|
||||||
|
/// Set whether a background is enabled for blending on this layer.
|
||||||
|
pub fn set_background_enable(&mut self, background: BackgroundID, enable: bool) -> &mut Self {
|
||||||
|
self.blend
|
||||||
|
.set_background_enable(self.layer, background, enable);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether objects are enabled for blending on this layer.
|
||||||
|
pub fn set_object_enable(&mut self, enable: bool) -> &mut Self {
|
||||||
|
self.blend.set_object_enable(self.layer, enable);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the backdrop contributes to the blend on this layer.
|
||||||
|
/// The backdrop is transparent colour, the colour rendered when nothing is
|
||||||
|
/// in it's place.
|
||||||
|
pub fn set_backdrop_enable(&mut self, enable: bool) -> &mut Self {
|
||||||
|
self.blend.set_backdrop_enable(self.layer, enable);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the weight for the blend on this layer.
|
||||||
|
pub fn set_blend_weight(&mut self, value: Num<u8, 4>) -> &mut Self {
|
||||||
|
self.blend.set_blend_weight(self.layer, value);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BLEND_CONTROL: *mut u16 = 0x0400_0050 as *mut _;
|
||||||
|
const BLEND_ALPHAS: *mut u16 = 0x0400_0052 as *mut _;
|
||||||
|
|
||||||
|
const BLEND_FADES: *mut u16 = 0x0400_0054 as *mut _;
|
||||||
|
|
||||||
|
impl Blend {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
let blend = Self {
|
||||||
|
targets: 0,
|
||||||
|
blend_weights: 0,
|
||||||
|
fade_weight: 0,
|
||||||
|
};
|
||||||
|
blend.commit();
|
||||||
|
|
||||||
|
blend
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the targets to all disabled, the targets control which layers are
|
||||||
|
/// enabled for blending.
|
||||||
|
pub fn reset_targets(&mut self) -> &mut Self {
|
||||||
|
self.targets = 0;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the blend weights
|
||||||
|
pub fn reset_weights(&mut self) -> &mut Self {
|
||||||
|
self.blend_weights = 0;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the brighten and darken weights
|
||||||
|
pub fn reset_fades(&mut self) -> &mut Self {
|
||||||
|
self.fade_weight = 0;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset targers, blend weights, and fades
|
||||||
|
pub fn reset(&mut self) -> &mut Self {
|
||||||
|
self.reset_targets().reset_fades().reset_weights()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a layer object whose functions work only on that layer,
|
||||||
|
/// convenient when performing multiple operations on that layer without the
|
||||||
|
/// need of specifying the layer every time.
|
||||||
|
pub fn layer(&mut self, layer: Layer) -> BlendLayer {
|
||||||
|
BlendLayer { blend: self, layer }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether a background is enabled for blending on a particular layer.
|
||||||
|
pub fn set_background_enable(
|
||||||
|
&mut self,
|
||||||
|
layer: Layer,
|
||||||
|
background: BackgroundID,
|
||||||
|
enable: bool,
|
||||||
|
) -> &mut Self {
|
||||||
|
let bit_to_modify = (background.0 as usize) + (layer as usize * 8);
|
||||||
|
self.targets = set_bits(self.targets, enable as u16, 1, bit_to_modify);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether objects are enabled for blending on a particular layer
|
||||||
|
pub fn set_object_enable(&mut self, layer: Layer, enable: bool) -> &mut Self {
|
||||||
|
let bit_to_modify = 0x4 + (layer as usize * 8);
|
||||||
|
self.targets = set_bits(self.targets, enable as u16, 1, bit_to_modify);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the backdrop contributes to the blend on a particular layer.
|
||||||
|
/// The backdrop is transparent colour, the colour rendered when nothing is
|
||||||
|
/// in it's place.
|
||||||
|
pub fn set_backdrop_enable(&mut self, layer: Layer, enable: bool) -> &mut Self {
|
||||||
|
let bit_to_modify = 0x5 + (layer as usize * 8);
|
||||||
|
self.targets = set_bits(self.targets, enable as u16, 1, bit_to_modify);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the weight for the blend on a particular layer.
|
||||||
|
pub fn set_blend_weight(&mut self, layer: Layer, value: Num<u8, 4>) -> &mut Self {
|
||||||
|
self.blend_weights = set_bits(
|
||||||
|
self.blend_weights,
|
||||||
|
value.to_raw() as u16,
|
||||||
|
5,
|
||||||
|
(layer as usize) * 8,
|
||||||
|
);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the fade of brighten or darken
|
||||||
|
pub fn set_fade(&mut self, value: Num<u8, 4>) -> &mut Self {
|
||||||
|
self.fade_weight = value.to_raw() as u16;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the current blend mode
|
||||||
|
pub fn set_blend_mode(&mut self, blend_mode: BlendMode) -> &mut Self {
|
||||||
|
self.targets = set_bits(self.targets, blend_mode as u16, 2, 0x6);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commits the current state, should be called near after a call to wait
|
||||||
|
/// for next vblank.
|
||||||
|
pub fn commit(&self) {
|
||||||
|
unsafe {
|
||||||
|
BLEND_CONTROL.write_volatile(self.targets);
|
||||||
|
BLEND_ALPHAS.write_volatile(self.blend_weights);
|
||||||
|
BLEND_FADES.write_volatile(self.fade_weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Blend {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.reset().commit();
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ use bitflags::bitflags;
|
||||||
use modular_bitfield::BitfieldSpecifier;
|
use modular_bitfield::BitfieldSpecifier;
|
||||||
use video::Video;
|
use video::Video;
|
||||||
|
|
||||||
use self::{object::ObjectController, window::Windows};
|
use self::{blend::Blend, object::ObjectController, window::Windows};
|
||||||
|
|
||||||
/// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer.
|
/// Graphics mode 3. Bitmap mode that provides a 16-bit colour framebuffer.
|
||||||
pub mod bitmap3;
|
pub mod bitmap3;
|
||||||
|
@ -23,6 +23,7 @@ pub mod tiled;
|
||||||
/// Giving out graphics mode.
|
/// Giving out graphics mode.
|
||||||
pub mod video;
|
pub mod video;
|
||||||
|
|
||||||
|
pub mod blend;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
mod font;
|
mod font;
|
||||||
|
@ -70,10 +71,11 @@ pub struct Display {
|
||||||
pub video: Video,
|
pub video: Video,
|
||||||
pub object: ObjectDistribution,
|
pub object: ObjectDistribution,
|
||||||
pub window: WindowDist,
|
pub window: WindowDist,
|
||||||
|
pub blend: BlendDist,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct ObjectDistribution {}
|
pub struct ObjectDistribution;
|
||||||
|
|
||||||
impl ObjectDistribution {
|
impl ObjectDistribution {
|
||||||
pub fn get(&mut self) -> ObjectController {
|
pub fn get(&mut self) -> ObjectController {
|
||||||
|
@ -82,7 +84,7 @@ impl ObjectDistribution {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct WindowDist {}
|
pub struct WindowDist;
|
||||||
|
|
||||||
impl WindowDist {
|
impl WindowDist {
|
||||||
pub fn get(&mut self) -> Windows {
|
pub fn get(&mut self) -> Windows {
|
||||||
|
@ -90,12 +92,22 @@ impl WindowDist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct BlendDist;
|
||||||
|
|
||||||
|
impl BlendDist {
|
||||||
|
pub fn get(&mut self) -> Blend {
|
||||||
|
Blend::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display {
|
impl Display {
|
||||||
pub(crate) const unsafe fn new() -> Self {
|
pub(crate) const unsafe fn new() -> Self {
|
||||||
Display {
|
Display {
|
||||||
video: Video {},
|
video: Video,
|
||||||
object: ObjectDistribution {},
|
object: ObjectDistribution,
|
||||||
window: WindowDist {},
|
window: WindowDist,
|
||||||
|
blend: BlendDist,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use super::{
|
||||||
///
|
///
|
||||||
/// Most games will use tiled modes, as bitmap modes are too slow to run at the full 60 FPS.
|
/// Most games will use tiled modes, as bitmap modes are too slow to run at the full 60 FPS.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct Video {}
|
pub struct Video;
|
||||||
|
|
||||||
impl Video {
|
impl Video {
|
||||||
/// Bitmap mode that provides a 16-bit colour framebuffer
|
/// Bitmap mode that provides a 16-bit colour framebuffer
|
||||||
|
|
|
@ -38,6 +38,21 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_bits<T>(current_value: T, value: T, length: usize, shift: usize) -> T
|
||||||
|
where
|
||||||
|
T: From<u8>
|
||||||
|
+ Copy
|
||||||
|
+ ops::Shl<usize, Output = T>
|
||||||
|
+ ops::BitAnd<Output = T>
|
||||||
|
+ ops::Sub<Output = T>
|
||||||
|
+ ops::BitOr<Output = T>
|
||||||
|
+ ops::Not<Output = T>,
|
||||||
|
{
|
||||||
|
let one: T = 1u8.into();
|
||||||
|
let mask: T = (one << length) - one;
|
||||||
|
(current_value & !(mask << shift)) | ((value & mask) << shift)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MemoryMapped1DArray<T, const N: usize> {
|
pub struct MemoryMapped1DArray<T, const N: usize> {
|
||||||
array: *mut [T; N],
|
array: *mut [T; N],
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue