diff --git a/CHANGELOG.md b/CHANGELOG.md index 1febcf78..43e748b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,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. +- Support for the blend mode of the GBA. Blending allows for alpha blending between layers and fading to black and white. ## Fixed - Fixed the fast magnitude function in agb_fixnum. This is also used in fast_normalise. Previously only worked for positive (x, y). diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 2e5c4171..47036f28 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -5,6 +5,7 @@ use core::{ cmp::{Eq, Ord, PartialEq, PartialOrd}, fmt::{Debug, Display}, + mem::size_of, ops::{ Add, AddAssign, BitAnd, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, Shr, Sub, SubAssign, @@ -298,6 +299,42 @@ impl Num { } } + /// Attempts to perform the conversion between two integer types and between + /// two different fractional precisions + /// ``` + /// # use agb_fixnum::*; + /// let a: Num = 1.into(); + /// let b: Option> = a.try_change_base(); + /// assert_eq!(b, Some(1.into())); + /// + /// let a: Num = 18.into(); + /// let b: Option> = a.try_change_base(); + /// assert_eq!(b, None); + /// ``` + pub fn try_change_base, const M: usize>( + self, + ) -> Option> { + if size_of::() > size_of::() { + // 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 pub fn from_raw(n: I) -> Self { Num(n) diff --git a/agb/examples/windows.rs b/agb/examples/windows.rs index ce4ee278..33d8a4f9 100644 --- a/agb/examples/windows.rs +++ b/agb/examples/windows.rs @@ -1,9 +1,10 @@ #![no_std] #![no_main] +use agb::display::blend::{BlendMode, Layer}; use agb::display::{example_logo, tiled::RegularBackgroundSize, window::WinIn}; use agb::display::{HEIGHT, WIDTH}; -use agb::fixnum::{Num, Rect, Vector2D}; +use agb::fixnum::{num, Num, Rect, Vector2D}; use agb::interrupt::VBlank; type FNum = Num; @@ -27,11 +28,27 @@ fn main(mut gba: agb::Gba) -> ! { .set_position(&Rect::new((10, 10).into(), (64, 64).into())) .enable(); + window + .win_out() + .enable() + .set_background_enable(map.background(), true) + .set_blend_enable(true); + 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 = (10, 10).into(); let mut velocity: Vector2D = Vector2D::new(1.into(), 1.into()); + let mut blend_amount: Num = num!(0.5); + let mut blend_velocity: Num = Num::new(1) / 128; + let vblank = VBlank::get(); loop { @@ -45,11 +62,21 @@ fn main(mut gba: agb::Gba) -> ! { 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 .win_in(WinIn::Win0) .set_position(&Rect::new(pos.floor(), (64, 64).into())); vblank.wait_for_vblank(); window.commit(); + blend.commit(); } } diff --git a/agb/src/display/blend.rs b/agb/src/display/blend.rs new file mode 100644 index 00000000..5275242b --- /dev/null +++ b/agb/src/display/blend.rs @@ -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) -> &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) -> &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) -> &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(); + } +} diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index aef286cd..620c1070 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -4,7 +4,7 @@ use bitflags::bitflags; use modular_bitfield::BitfieldSpecifier; 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. pub mod bitmap3; @@ -23,6 +23,7 @@ pub mod tiled; /// Giving out graphics mode. pub mod video; +pub mod blend; pub mod window; mod font; @@ -70,10 +71,11 @@ pub struct Display { pub video: Video, pub object: ObjectDistribution, pub window: WindowDist, + pub blend: BlendDist, } #[non_exhaustive] -pub struct ObjectDistribution {} +pub struct ObjectDistribution; impl ObjectDistribution { pub fn get(&mut self) -> ObjectController { @@ -82,7 +84,7 @@ impl ObjectDistribution { } #[non_exhaustive] -pub struct WindowDist {} +pub struct WindowDist; impl WindowDist { 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 { pub(crate) const unsafe fn new() -> Self { Display { - video: Video {}, - object: ObjectDistribution {}, - window: WindowDist {}, + video: Video, + object: ObjectDistribution, + window: WindowDist, + blend: BlendDist, } } } diff --git a/agb/src/display/video.rs b/agb/src/display/video.rs index 6367eff0..9f62e3fa 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -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. #[non_exhaustive] -pub struct Video {} +pub struct Video; impl Video { /// Bitmap mode that provides a 16-bit colour framebuffer diff --git a/agb/src/memory_mapped.rs b/agb/src/memory_mapped.rs index 57b5bb51..290d865f 100644 --- a/agb/src/memory_mapped.rs +++ b/agb/src/memory_mapped.rs @@ -38,6 +38,21 @@ where } } +pub fn set_bits(current_value: T, value: T, length: usize, shift: usize) -> T +where + T: From + + Copy + + ops::Shl + + ops::BitAnd + + ops::Sub + + ops::BitOr + + ops::Not, +{ + let one: T = 1u8.into(); + let mask: T = (one << length) - one; + (current_value & !(mask << shift)) | ((value & mask) << shift) +} + pub struct MemoryMapped1DArray { array: *mut [T; N], }