From ca9c898c148acc4f30fcaccb0a481062bc063d7b Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 18:34:03 +0100 Subject: [PATCH 01/11] add general set bits --- agb/src/memory_mapped.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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], } From 5d2161c318c541f55e93a0cd1a493acb8227b79a Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 18:35:29 +0100 Subject: [PATCH 02/11] add function to attempt to change base --- agb-fixnum/src/lib.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 2e5c4171..be592af9 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,32 @@ impl Num { } } + /// Attempts to perform the conversion between two integer types and between + /// two different fractional precisions + 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) From d0b3d9e7b3689252eaf7fd7dcd3e7047ef41d38e Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 18:35:36 +0100 Subject: [PATCH 03/11] add blend abstraction --- agb/src/display/blend.rs | 109 +++++++++++++++++++++++++++++++++++++++ agb/src/display/mod.rs | 1 + 2 files changed, 110 insertions(+) create mode 100644 agb/src/display/blend.rs diff --git a/agb/src/display/blend.rs b/agb/src/display/blend.rs new file mode 100644 index 00000000..1104416f --- /dev/null +++ b/agb/src/display/blend.rs @@ -0,0 +1,109 @@ +use crate::{fixnum::Num, memory_mapped::set_bits}; + +use super::tiled::BackgroundID; + +#[derive(Clone, Copy, Debug)] +pub enum Layer { + Top = 0, + Bottom = 1, +} + +#[derive(Clone, Copy, Debug)] +pub enum BlendMode { + Off = 0, + Normal = 0b01, + FadeToWhite = 0b10, + FadeToBlack = 0b11, +} + +pub struct Blend { + targets: u16, + blend_weights: u16, + fade_weight: u16, +} + +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 fn reset_targets(&mut self) -> &mut Self { + self.targets = 0; + + self + } + + pub fn reset_weights(&mut self) -> &mut Self { + self.blend_weights = 0; + + self + } + + pub fn reset_fades(&mut self) -> &mut Self { + self.fade_weight = 0; + + self + } + + pub fn reset(&mut self) -> &mut Self { + self.reset_targets().reset_fades().reset_weights() + } + + 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 + } + + 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 + } + + 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 + } + + 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 + } + + pub fn set_fade(&mut self, value: Num) -> &mut Self { + self.fade_weight = value.to_raw() as u16; + + self + } + + 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 + } + + 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); + } + } +} diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index aef286cd..0e8cc37a 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -23,6 +23,7 @@ pub mod tiled; /// Giving out graphics mode. pub mod video; +pub mod blend; pub mod window; mod font; From 41ccd56919d061ae99faa4f17be7db9d8a931b7c Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 18:37:25 +0100 Subject: [PATCH 04/11] new function for blend --- agb/src/display/blend.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/agb/src/display/blend.rs b/agb/src/display/blend.rs index 1104416f..4b6ee180 100644 --- a/agb/src/display/blend.rs +++ b/agb/src/display/blend.rs @@ -28,6 +28,17 @@ 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 + } + pub fn reset_targets(&mut self) -> &mut Self { self.targets = 0; @@ -107,3 +118,9 @@ impl Blend { } } } + +impl Drop for Blend { + fn drop(&mut self) { + self.reset().commit(); + } +} From cae71ff2499b8e732447abfcfd5edfa9c2b4d146 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 18:40:47 +0100 Subject: [PATCH 05/11] add blend to distributor --- agb/src/display/mod.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index 0e8cc37a..a747005a 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; @@ -71,6 +71,7 @@ pub struct Display { pub video: Video, pub object: ObjectDistribution, pub window: WindowDist, + pub blend: BlendDist, } #[non_exhaustive] @@ -91,12 +92,19 @@ 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 {}, + blend: BlendDist, } } } From 7ad160e30f0758e17445b28f5d6d31cb45bcbd4d Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 18:40:59 +0100 Subject: [PATCH 06/11] use struct newtypes --- agb/src/display/mod.rs | 7 +++++-- agb/src/display/video.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs index a747005a..620c1070 100644 --- a/agb/src/display/mod.rs +++ b/agb/src/display/mod.rs @@ -75,7 +75,7 @@ pub struct Display { } #[non_exhaustive] -pub struct ObjectDistribution {} +pub struct ObjectDistribution; impl ObjectDistribution { pub fn get(&mut self) -> ObjectController { @@ -84,7 +84,7 @@ impl ObjectDistribution { } #[non_exhaustive] -pub struct WindowDist {} +pub struct WindowDist; impl WindowDist { pub fn get(&mut self) -> Windows { @@ -104,6 +104,9 @@ impl BlendDist { impl Display { pub(crate) const unsafe fn new() -> Self { Display { + 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 From ff5a1fbbba6e71713e8503ab5e35ff5e8cebedf6 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 19:04:40 +0100 Subject: [PATCH 07/11] add an example to the new number function --- agb-fixnum/src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index be592af9..47036f28 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -301,6 +301,16 @@ 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> { From 921c26f7c2c0e9760b733255d00d8eed5e2b2f8f Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 19:04:48 +0100 Subject: [PATCH 08/11] add blending to the window example --- agb/examples/windows.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) 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(); } } From afa9ef9109937aec2c47808ad618f104d5d00121 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 21:15:38 +0100 Subject: [PATCH 09/11] add docs for blend --- agb/src/display/blend.rs | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/agb/src/display/blend.rs b/agb/src/display/blend.rs index 4b6ee180..68848dcb 100644 --- a/agb/src/display/blend.rs +++ b/agb/src/display/blend.rs @@ -1,21 +1,48 @@ +//! 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, @@ -39,28 +66,34 @@ impl Blend { 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() } + /// Set whether a background is enabled for blending on a particular layer. pub fn set_background_enable( &mut self, layer: Layer, @@ -73,6 +106,7 @@ impl Blend { 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); @@ -80,6 +114,9 @@ impl Blend { 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); @@ -87,6 +124,7 @@ impl Blend { 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, @@ -98,18 +136,22 @@ impl Blend { 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); From 2cc560273182cb8b87f285ea364763d444c32279 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Aug 2022 21:19:12 +0100 Subject: [PATCH 10/11] add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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). From d5c9312dc43e9f78dd53f14f8f7567db1f7fcfb8 Mon Sep 17 00:00:00 2001 From: Corwin Date: Mon, 8 Aug 2022 18:53:20 +0100 Subject: [PATCH 11/11] add convenience functions for performing multiple actions on one layer --- agb/src/display/blend.rs | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/agb/src/display/blend.rs b/agb/src/display/blend.rs index 68848dcb..5275242b 100644 --- a/agb/src/display/blend.rs +++ b/agb/src/display/blend.rs @@ -49,6 +49,47 @@ pub struct Blend { 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 _; @@ -93,6 +134,13 @@ impl Blend { 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,