#![warn(missing_docs)]
mod registers;
use registers::{BlendControlAlpha, BlendControlBrightness, BlendControlRegister};
use super::tiled::BackgroundId;
use crate::{fixnum::Num, memory_mapped::MemoryMapped};
const BLEND_CONTROL: MemoryMapped<BlendControlRegister> = unsafe { MemoryMapped::new(0x0400_0050) };
const BLEND_ALPHA: MemoryMapped<BlendControlAlpha> = unsafe { MemoryMapped::new(0x0400_0052) };
const BLEND_BRIGHTNESS: MemoryMapped<BlendControlBrightness> =
unsafe { MemoryMapped::new(0x0400_0054) };
#[derive(Clone, Copy, Debug)]
pub enum Layer {
Top = 0,
Bottom = 1,
}
pub struct Blend {
blend_control: registers::BlendControlRegister,
alpha: registers::BlendControlAlpha,
brightness: registers::BlendControlBrightness,
}
impl Blend {
pub(crate) fn new() -> Self {
Self {
blend_control: Default::default(),
alpha: Default::default(),
brightness: Default::default(),
}
}
fn reset(&mut self) {
self.blend_control = Default::default();
self.alpha = Default::default();
self.brightness = Default::default();
}
pub fn alpha(
&mut self,
top_layer_alpha: Num<u8, 4>,
bottom_layer_alpha: Num<u8, 4>,
) -> BlendAlphaEffect<'_> {
self.reset();
self.blend_control
.set_colour_effect(registers::Effect::Alpha);
self.set_layer_alpha(Layer::Top, top_layer_alpha);
self.set_layer_alpha(Layer::Bottom, bottom_layer_alpha);
BlendAlphaEffect { blend: self }
}
pub fn brighten(&mut self, amount: Num<u8, 4>) -> BlendFadeEffect<'_> {
self.reset();
self.blend_control
.set_colour_effect(registers::Effect::Increase);
self.set_fade(amount);
BlendFadeEffect { blend: self }
}
pub fn darken(&mut self, amount: Num<u8, 4>) -> BlendFadeEffect<'_> {
self.reset();
self.blend_control
.set_colour_effect(registers::Effect::Decrease);
self.set_fade(amount);
BlendFadeEffect { blend: self }
}
pub fn object_transparency(
&mut self,
top_layer_alpha: Num<u8, 4>,
bottom_layer_alpha: Num<u8, 4>,
) -> BlendObjectTransparency<'_> {
self.reset();
assert!(top_layer_alpha <= 1.into());
assert!(bottom_layer_alpha <= 1.into());
self.blend_control
.set_colour_effect(registers::Effect::None);
self.set_layer_alpha(Layer::Top, top_layer_alpha);
self.set_layer_alpha(Layer::Bottom, bottom_layer_alpha);
BlendObjectTransparency { blend: self }
}
fn set_background_enable(&mut self, layer: Layer, background_id: impl Into<BackgroundId>) {
self.with_target(layer, |mut target| {
target.enable_background(background_id);
target
});
}
fn set_object_enable(&mut self, layer: Layer) {
self.with_target(layer, |mut target| {
target.enable_object();
target
});
}
fn set_backdrop_enable(&mut self, layer: Layer) {
self.with_target(layer, |mut target| {
target.enable_backdrop();
target
});
}
fn with_target(
&mut self,
layer: Layer,
f: impl FnOnce(registers::BlendTarget) -> registers::BlendTarget,
) {
match layer {
Layer::Top => self
.blend_control
.set_first_target(f(self.blend_control.first_target())),
Layer::Bottom => self
.blend_control
.set_second_target(f(self.blend_control.second_target())),
}
}
fn set_layer_alpha(&mut self, layer: Layer, value: Num<u8, 4>) {
assert!(value <= 1.into());
match layer {
Layer::Top => self.alpha.set_first_blend(value),
Layer::Bottom => self.alpha.set_second_blend(value),
}
}
fn set_fade(&mut self, value: Num<u8, 4>) {
assert!(value <= 1.into());
self.brightness.set(value);
}
pub(crate) fn commit(&self) {
BLEND_CONTROL.set(self.blend_control);
BLEND_ALPHA.set(self.alpha);
BLEND_BRIGHTNESS.set(self.brightness);
}
}
pub struct BlendAlphaEffect<'blend> {
blend: &'blend mut Blend,
}
impl BlendAlphaEffect<'_> {
pub fn enable_background(
&mut self,
layer: Layer,
background: impl Into<BackgroundId>,
) -> &mut Self {
self.blend.set_background_enable(layer, background);
self
}
pub fn enable_object(&mut self, layer: Layer) -> &mut Self {
self.blend.set_object_enable(layer);
self
}
pub fn enable_backdrop(&mut self, layer: Layer) -> &mut Self {
self.blend.set_backdrop_enable(layer);
self
}
}
pub struct BlendFadeEffect<'blend> {
blend: &'blend mut Blend,
}
impl BlendFadeEffect<'_> {
pub fn enable_background(&mut self, background: impl Into<BackgroundId>) -> &mut Self {
self.blend.set_background_enable(Layer::Top, background);
self
}
pub fn enable_object(&mut self) -> &mut Self {
self.blend.set_object_enable(Layer::Top);
self
}
pub fn enable_backdrop(&mut self) -> &mut Self {
self.blend.set_backdrop_enable(Layer::Top);
self
}
pub fn object_transparency(
&mut self,
top_fade_amount: Num<u8, 4>,
bottom_fade_amount: Num<u8, 4>,
) -> BlendObjectTransparency<'_> {
self.blend.set_layer_alpha(Layer::Top, top_fade_amount);
self.blend
.set_layer_alpha(Layer::Bottom, bottom_fade_amount);
BlendObjectTransparency { blend: self.blend }
}
}
pub struct BlendObjectTransparency<'blend> {
blend: &'blend mut Blend,
}
impl BlendObjectTransparency<'_> {
pub fn enable_background(&mut self, background: impl Into<BackgroundId>) -> &mut Self {
self.blend.set_background_enable(Layer::Bottom, background);
self
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
Gba,
display::{
AffineMatrix, Priority, WinIn,
object::{AffineMatrixObject, AffineMode, GraphicsMode, Object, ObjectAffine},
tiled::{
AffineBackground, AffineBackgroundSize, AffineBackgroundWrapBehaviour,
RegularBackground, RegularBackgroundSize, VRAM_MANAGER,
},
},
fixnum::{Rect, num, vec2},
include_aseprite, include_background_gfx,
test_runner::assert_image_output,
};
include_background_gfx!(mod background,
LOGO => deduplicate "gfx/test_logo.aseprite",
LOGO_256 => 256 "gfx/test_logo.aseprite",
);
include_aseprite!(
mod sprites,
"examples/gfx/crab.aseprite",
);
#[test_case]
fn can_blend_to_white(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame.blend().brighten(num!(0.5)).enable_background(bg_id);
frame.commit();
assert_image_output("gfx/test_output/blend/regular_to_white.png");
}
#[test_case]
fn can_blend_to_white_in_window(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame.blend().brighten(num!(0.5)).enable_background(bg_id);
frame
.windows()
.win_in(WinIn::Win0)
.enable_background(bg_id)
.enable_blending()
.set_pos(Rect::new(vec2(20, 20), vec2(100, 100)));
frame.windows.win_out().enable_background(bg_id);
frame.commit();
assert_image_output("gfx/test_output/blend/regular_to_white_in_window.png");
}
#[test_case]
fn can_blend_two_layers_into_each_other(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
let bg1_id = bg.show(&mut frame);
bg.set_scroll_pos((40, 40));
let bg2_id = bg.show(&mut frame);
frame
.blend()
.alpha(num!(0.8), num!(0.2))
.enable_background(Layer::Top, bg1_id)
.enable_background(Layer::Bottom, bg2_id);
frame.commit();
assert_image_output("gfx/test_output/blend/blend_two_layers_into_each_other.png");
}
#[test_case]
fn can_blend_affine_backgrounds(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = AffineBackground::new(
Priority::P0,
AffineBackgroundSize::Background32x32,
AffineBackgroundWrapBehaviour::Wrap,
);
for i in 0..32 {
for j in 0..32 {
bg.set_tile(
(i, j),
&background::LOGO_256.tiles,
3 * 30 + 3 + (i + j) as u16 % 5,
);
}
}
bg.set_transform(AffineMatrix::from_rotation(num!(0.125)));
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame.blend().darken(num!(0.5)).enable_background(bg_id);
frame.commit();
assert_image_output("gfx/test_output/blend/blend_affine_darken.png");
}
#[test_case]
fn can_blend_objects_to_create_transparency_effects(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame
.blend()
.object_transparency(num!(0.5), num!(0.5))
.enable_background(bg_id);
Object::new(sprites::IDLE.sprite(0))
.set_pos((100, 100))
.set_graphics_mode(GraphicsMode::AlphaBlending)
.show(&mut frame);
frame.commit();
assert_image_output("gfx/test_output/blend/blend_object_transparency.png");
}
#[test_case]
fn can_blend_object_to_white(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
bg.show(&mut frame);
frame.blend().brighten(num!(0.5)).enable_object();
Object::new(sprites::IDLE.sprite(0))
.set_pos((100, 100))
.set_graphics_mode(GraphicsMode::AlphaBlending)
.show(&mut frame);
frame.commit();
assert_image_output("gfx/test_output/blend/blend_object_lighten.png");
}
#[test_case]
fn can_blend_object_to_black(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
bg.show(&mut frame);
frame.blend().darken(num!(0.75)).enable_object();
Object::new(sprites::IDLE.sprite(0))
.set_pos((100, 100))
.set_graphics_mode(GraphicsMode::AlphaBlending)
.show(&mut frame);
frame.commit();
assert_image_output("gfx/test_output/blend/blend_object_darken.png");
}
#[test_case]
fn can_blend_object_shape_to_black(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame.blend().darken(num!(0.75)).enable_background(bg_id);
frame
.windows()
.win_obj()
.enable_blending()
.enable_background(bg_id);
frame.windows().win_out().enable_background(bg_id);
Object::new(sprites::IDLE.sprite(0))
.set_pos((100, 100))
.set_graphics_mode(GraphicsMode::Window)
.show(&mut frame);
frame.commit();
assert_image_output("gfx/test_output/blend/blend_object_darken_window.png");
}
#[test_case]
fn can_blend_affine_object_to_black(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
let mut frame = gfx.frame();
bg.show(&mut frame);
frame.blend().darken(num!(0.75)).enable_object();
let matrix = AffineMatrixObject::from(AffineMatrix::from_rotation(num!(0.125)));
ObjectAffine::new(sprites::IDLE.sprite(0), matrix, AffineMode::AffineDouble)
.set_pos((100, 100))
.set_graphics_mode(GraphicsMode::AlphaBlending)
.show(&mut frame);
frame.commit();
assert_image_output("gfx/test_output/blend/blend_object_affine_darken.png");
}
#[test_case]
fn can_fade_and_have_object_transparency(gba: &mut Gba) {
VRAM_MANAGER.set_background_palettes(background::PALETTES);
let mut gfx = gba.graphics.get();
let mut bg = RegularBackground::new(
Priority::P0,
RegularBackgroundSize::Background32x32,
background::LOGO.tiles.format(),
);
bg.fill_with(&background::LOGO);
bg.set_scroll_pos((-116, -116));
let mut frame = gfx.frame();
let bg_id = bg.show(&mut frame);
frame
.blend()
.darken(num!(0.75))
.enable_object()
.object_transparency(num!(0.5), num!(0.5))
.enable_background(bg_id);
Object::new(sprites::IDLE.sprite(0))
.set_pos((100, 100))
.set_graphics_mode(GraphicsMode::AlphaBlending)
.show(&mut frame);
frame.commit();
assert_image_output("gfx/test_output/blend/blend_object_fade_transparent.png");
}
}