mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 08:11:33 +11:00
DMA abstraction (#491)
It would be nice to have an easy way to add DMA effects to games. - [x] Changelog updated
This commit is contained in:
commit
cdafc3c139
|
@ -11,9 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Added `.priority()`, `.set_priority()` and `.is_visible()` to `RegularMap`, `AffineMap` and `InfiniteScrolledMap`.
|
- Added `.priority()`, `.set_priority()` and `.is_visible()` to `RegularMap`, `AffineMap` and `InfiniteScrolledMap`.
|
||||||
- Replaced `.show()` and `.hide()` with `.set_visible()`in `RegularMap`, `AffineMap` and `InfiniteScrolledMap`.
|
- Replaced `.show()` and `.hide()` with `.set_visible()`in `RegularMap`, `AffineMap` and `InfiniteScrolledMap`.
|
||||||
- Added `.hflip()`, `.vflip()`, `.priority()`, `.position()` to `ObjectUnmanaged` and `Object`.
|
- Added `.hflip()`, `.vflip()`, `.priority()`, `.position()` to `ObjectUnmanaged` and `Object`.
|
||||||
|
- An abstraction over hblank DMA to allow for cool effects like gradients and circular windows. See the dma_effect* examples.
|
||||||
- Expermental and incomplete support for MIDI files with agb-tracker.
|
- Expermental and incomplete support for MIDI files with agb-tracker.
|
||||||
- Fixnum now implements [`num::Num`](https://docs.rs/num/0.4/num/trait.Num.html) from the [`num`](https://crates.io/crates/num) crate.
|
- Fixnum now implements [`num::Num`](https://docs.rs/num/0.4/num/trait.Num.html) from the [`num`](https://crates.io/crates/num) crate.
|
||||||
|
|
||||||
|
### Change
|
||||||
|
- A few functions which previously accepted a `Vector<u16>` now accept an `impl Into<Vector2D<u16>>` instead.
|
||||||
|
|
||||||
## [0.18.1] - 2024/02/06
|
## [0.18.1] - 2024/02/06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -26,7 +26,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
|
|
||||||
for y in 0..32u16 {
|
for y in 0..32u16 {
|
||||||
for x in 0..32u16 {
|
for x in 0..32u16 {
|
||||||
bg.set_tile(&mut vram, (x, y).into(), &tileset, 1);
|
bg.set_tile(&mut vram, (x, y), &tileset, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,14 +46,14 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
scroll_x += input.x_tri() as i32;
|
scroll_x += input.x_tri() as i32;
|
||||||
scroll_y += input.y_tri() as i32;
|
scroll_y += input.y_tri() as i32;
|
||||||
|
|
||||||
let scroll_pos = (scroll_x, scroll_y).into();
|
let scroll_pos = (scroll_x, scroll_y);
|
||||||
|
|
||||||
rotation += rotation_increase;
|
rotation += rotation_increase;
|
||||||
rotation = rotation.rem_euclid(1.into());
|
rotation = rotation.rem_euclid(1.into());
|
||||||
|
|
||||||
let transformation = AffineMatrixBackground::from_scale_rotation_position(
|
let transformation = AffineMatrixBackground::from_scale_rotation_position(
|
||||||
(0, 0).into(),
|
(0, 0),
|
||||||
(1, 1).into(),
|
(1, 1),
|
||||||
rotation,
|
rotation,
|
||||||
scroll_pos,
|
scroll_pos,
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,7 +30,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
for x in 0..30u16 {
|
for x in 0..30u16 {
|
||||||
bg.set_tile(
|
bg.set_tile(
|
||||||
&mut vram,
|
&mut vram,
|
||||||
(x, y).into(),
|
(x, y),
|
||||||
&tileset,
|
&tileset,
|
||||||
water_tiles::water_tiles.tile_settings[0],
|
water_tiles::water_tiles.tile_settings[0],
|
||||||
);
|
);
|
||||||
|
|
|
@ -64,7 +64,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
let i = i as u16;
|
let i = i as u16;
|
||||||
background.set_tile(
|
background.set_tile(
|
||||||
&mut vram,
|
&mut vram,
|
||||||
(i % 32, i / 32).into(),
|
(i % 32, i / 32),
|
||||||
&tileset,
|
&tileset,
|
||||||
TileSetting::from_raw(tile),
|
TileSetting::from_raw(tile),
|
||||||
);
|
);
|
||||||
|
|
48
agb/examples/dma_effect_background_colour.rs
Normal file
48
agb/examples/dma_effect_background_colour.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
|
use agb::{
|
||||||
|
display::{
|
||||||
|
example_logo,
|
||||||
|
tiled::{RegularBackgroundSize, TileFormat},
|
||||||
|
},
|
||||||
|
interrupt::VBlank,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[agb::entry]
|
||||||
|
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,
|
||||||
|
TileFormat::FourBpp,
|
||||||
|
);
|
||||||
|
|
||||||
|
let dma = gba.dma.dma().dma0;
|
||||||
|
|
||||||
|
example_logo::display_logo(&mut map, &mut vram);
|
||||||
|
|
||||||
|
let vblank = VBlank::get();
|
||||||
|
|
||||||
|
let colours: Box<[_]> = (0..160).map(|i| ((i * 0xffff) / 160) as u16).collect();
|
||||||
|
|
||||||
|
let mut frame = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// hardcoding palette index 2 here which you wouldn't want to do in a real example (instead, look for
|
||||||
|
// the colour you want to replace)
|
||||||
|
let _background_color_transfer =
|
||||||
|
unsafe { dma.hblank_transfer(&vram.background_palette_colour_dma(0, 2), &colours) };
|
||||||
|
|
||||||
|
vblank.wait_for_vblank();
|
||||||
|
frame += 1;
|
||||||
|
if frame > 160 {
|
||||||
|
frame = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
agb/examples/dma_effect_background_scroll.rs
Normal file
46
agb/examples/dma_effect_background_scroll.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
|
use agb::{
|
||||||
|
display::{
|
||||||
|
example_logo,
|
||||||
|
tiled::{RegularBackgroundSize, TileFormat},
|
||||||
|
},
|
||||||
|
interrupt::VBlank,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[agb::entry]
|
||||||
|
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,
|
||||||
|
TileFormat::FourBpp,
|
||||||
|
);
|
||||||
|
|
||||||
|
let dma = gba.dma.dma().dma0;
|
||||||
|
|
||||||
|
example_logo::display_logo(&mut map, &mut vram);
|
||||||
|
|
||||||
|
let vblank = VBlank::get();
|
||||||
|
|
||||||
|
let offsets: Box<[_]> = (0..160 * 2).collect();
|
||||||
|
|
||||||
|
let mut frame = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let _x_scroll_transfer =
|
||||||
|
unsafe { dma.hblank_transfer(&map.x_scroll_dma(), &offsets[frame..]) };
|
||||||
|
|
||||||
|
vblank.wait_for_vblank();
|
||||||
|
frame += 1;
|
||||||
|
if frame > 160 {
|
||||||
|
frame = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
agb/examples/dma_effect_circular_window.rs
Normal file
97
agb/examples/dma_effect_circular_window.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![feature(isqrt)]
|
||||||
|
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
|
use agb::{
|
||||||
|
display::{
|
||||||
|
example_logo,
|
||||||
|
tiled::{RegularBackgroundSize, TileFormat},
|
||||||
|
window::WinIn,
|
||||||
|
HEIGHT, WIDTH,
|
||||||
|
},
|
||||||
|
fixnum::{Num, Rect, Vector2D},
|
||||||
|
interrupt::VBlank,
|
||||||
|
};
|
||||||
|
use alloc::{boxed::Box, vec};
|
||||||
|
|
||||||
|
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,
|
||||||
|
TileFormat::FourBpp,
|
||||||
|
);
|
||||||
|
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();
|
||||||
|
|
||||||
|
let dmas = gba.dma.dma();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
let circle: Box<[_]> = (1..64i32)
|
||||||
|
.map(|i| {
|
||||||
|
let y = 32 - i;
|
||||||
|
let x = (32 * 32 - y * y).isqrt();
|
||||||
|
let x1 = 32 - x;
|
||||||
|
let x2 = 32 + x;
|
||||||
|
|
||||||
|
((x1 as u16) << 8) | (x2 as u16)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut circle_poses = vec![0; 160];
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_pos = pos.x.floor().max(0) as u16;
|
||||||
|
let y_pos = pos.y.floor().max(0);
|
||||||
|
let x_adjustment = x_pos << 8 | x_pos;
|
||||||
|
for (i, value) in circle_poses.iter_mut().enumerate() {
|
||||||
|
let i = i as i32;
|
||||||
|
if i <= y_pos || i >= y_pos + 64 {
|
||||||
|
*value = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*value = circle[(i - y_pos) as usize - 1] + x_adjustment;
|
||||||
|
}
|
||||||
|
|
||||||
|
window
|
||||||
|
.win_in(WinIn::Win0)
|
||||||
|
.set_position(&Rect::new(pos.floor(), (64, 65).into()));
|
||||||
|
window.commit();
|
||||||
|
|
||||||
|
let dma_controllable = window.win_in(WinIn::Win0).horizontal_position_dma();
|
||||||
|
let _transfer = unsafe { dmas.dma0.hblank_transfer(&dma_controllable, &circle_poses) };
|
||||||
|
|
||||||
|
vblank.wait_for_vblank();
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
|
|
||||||
bg.set_tile(
|
bg.set_tile(
|
||||||
&mut vram,
|
&mut vram,
|
||||||
(x as u16, y as u16).into(),
|
(x as u16, y as u16),
|
||||||
&dynamic_tile.tile_set(),
|
&dynamic_tile.tile_set(),
|
||||||
dynamic_tile.tile_setting(),
|
dynamic_tile.tile_setting(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,7 +31,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
|
|
||||||
init_background(&mut bg, &mut vram);
|
init_background(&mut bg, &mut vram);
|
||||||
|
|
||||||
let mut title_renderer = FONT.render_text((0u16, 3u16).into());
|
let mut title_renderer = FONT.render_text((0u16, 3u16));
|
||||||
let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram);
|
let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram);
|
||||||
|
|
||||||
writeln!(&mut writer, "Crazy Glue by Josh Woodward").unwrap();
|
writeln!(&mut writer, "Crazy Glue by Josh Woodward").unwrap();
|
||||||
|
@ -57,7 +57,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut frame_counter = 0i32;
|
let mut frame_counter = 0i32;
|
||||||
let mut has_written_frame_time = false;
|
let mut has_written_frame_time = false;
|
||||||
|
|
||||||
let mut stats_renderer = FONT.render_text((0u16, 6u16).into());
|
let mut stats_renderer = FONT.render_text((0u16, 6u16));
|
||||||
loop {
|
loop {
|
||||||
vblank_provider.wait_for_vblank();
|
vblank_provider.wait_for_vblank();
|
||||||
bg.commit(&mut vram);
|
bg.commit(&mut vram);
|
||||||
|
@ -106,7 +106,7 @@ fn init_background(bg: &mut RegularMap, vram: &mut VRamManager) {
|
||||||
for x in 0..30u16 {
|
for x in 0..30u16 {
|
||||||
bg.set_tile(
|
bg.set_tile(
|
||||||
vram,
|
vram,
|
||||||
(x, y).into(),
|
(x, y),
|
||||||
&background_tile.tile_set(),
|
&background_tile.tile_set(),
|
||||||
background_tile.tile_setting(),
|
background_tile.tile_setting(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -58,7 +58,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
|
|
||||||
let start = timer.value();
|
let start = timer.value();
|
||||||
|
|
||||||
wr.layout((WIDTH, 40).into(), TextAlignment::Justify, 2);
|
wr.layout((WIDTH, 40), TextAlignment::Justify, 2);
|
||||||
let end = timer.value();
|
let end = timer.value();
|
||||||
|
|
||||||
agb::println!(
|
agb::println!(
|
||||||
|
@ -83,7 +83,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
line_done = false;
|
line_done = false;
|
||||||
wr.pop_line();
|
wr.pop_line();
|
||||||
}
|
}
|
||||||
wr.update((0, HEIGHT - 40).into());
|
wr.update((0, HEIGHT - 40));
|
||||||
let end = timer.value();
|
let end = timer.value();
|
||||||
|
|
||||||
frame += 1;
|
frame += 1;
|
||||||
|
|
|
@ -34,7 +34,7 @@ fn all_sprites(gfx: &OamManaged, rotation_speed: Num<i32, 16>) {
|
||||||
let mut obj = gfx.object_sprite(&SPRITES[0]);
|
let mut obj = gfx.object_sprite(&SPRITES[0]);
|
||||||
obj.set_affine_matrix(matrix.clone());
|
obj.set_affine_matrix(matrix.clone());
|
||||||
obj.show_affine(object::AffineMode::Affine);
|
obj.show_affine(object::AffineMode::Affine);
|
||||||
obj.set_position((x * 16 + 8, y * 16 + 8).into());
|
obj.set_position((x * 16 + 8, y * 16 + 8));
|
||||||
objs.push(obj);
|
objs.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ fn all_tags(gfx: &OamManaged) {
|
||||||
let (size_x, size_y) = (size_x as i32, size_y as i32);
|
let (size_x, size_y) = (size_x as i32, size_y as i32);
|
||||||
let mut obj = gfx.object_sprite(sprite);
|
let mut obj = gfx.object_sprite(sprite);
|
||||||
obj.show();
|
obj.show();
|
||||||
obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2).into());
|
obj.set_position((x * 32 + 16 - size_x / 2, y * 32 + 16 - size_y / 2));
|
||||||
objs.push((obj, v));
|
objs.push((obj, v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
|
|
||||||
init_background(&mut bg, &mut vram);
|
init_background(&mut bg, &mut vram);
|
||||||
|
|
||||||
let mut title_renderer = FONT.render_text((0u16, 3u16).into());
|
let mut title_renderer = FONT.render_text((0u16, 3u16));
|
||||||
let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram);
|
let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram);
|
||||||
|
|
||||||
writeln!(&mut writer, "Let it in by Josh Woodward").unwrap();
|
writeln!(&mut writer, "Let it in by Josh Woodward").unwrap();
|
||||||
|
@ -55,7 +55,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut frame_counter = 0i32;
|
let mut frame_counter = 0i32;
|
||||||
let mut has_written_frame_time = false;
|
let mut has_written_frame_time = false;
|
||||||
|
|
||||||
let mut stats_renderer = FONT.render_text((0u16, 6u16).into());
|
let mut stats_renderer = FONT.render_text((0u16, 6u16));
|
||||||
loop {
|
loop {
|
||||||
vblank_provider.wait_for_vblank();
|
vblank_provider.wait_for_vblank();
|
||||||
bg.commit(&mut vram);
|
bg.commit(&mut vram);
|
||||||
|
@ -94,7 +94,7 @@ fn init_background(bg: &mut RegularMap, vram: &mut VRamManager) {
|
||||||
for x in 0..30u16 {
|
for x in 0..30u16 {
|
||||||
bg.set_tile(
|
bg.set_tile(
|
||||||
vram,
|
vram,
|
||||||
(x, y).into(),
|
(x, y),
|
||||||
&background_tile.tile_set(),
|
&background_tile.tile_set(),
|
||||||
background_tile.tile_setting(),
|
background_tile.tile_setting(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,7 +35,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
for x in 0..30u16 {
|
for x in 0..30u16 {
|
||||||
bg.set_tile(
|
bg.set_tile(
|
||||||
&mut vram,
|
&mut vram,
|
||||||
(x, y).into(),
|
(x, y),
|
||||||
&background_tile.tile_set(),
|
&background_tile.tile_set(),
|
||||||
background_tile.tile_setting(),
|
background_tile.tile_setting(),
|
||||||
);
|
);
|
||||||
|
@ -44,7 +44,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
|
|
||||||
vram.remove_dynamic_tile(background_tile);
|
vram.remove_dynamic_tile(background_tile);
|
||||||
|
|
||||||
let mut renderer = FONT.render_text((0u16, 3u16).into());
|
let mut renderer = FONT.render_text((0u16, 3u16));
|
||||||
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
|
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
|
||||||
|
|
||||||
writeln!(&mut writer, "Hello, World!").unwrap();
|
writeln!(&mut writer, "Hello, World!").unwrap();
|
||||||
|
@ -58,7 +58,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
let mut frame = 0;
|
let mut frame = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut renderer = FONT.render_text((4u16, 0u16).into());
|
let mut renderer = FONT.render_text((4u16, 0u16));
|
||||||
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
|
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
|
||||||
|
|
||||||
writeln!(&mut writer, "Frame {frame}").unwrap();
|
writeln!(&mut writer, "Frame {frame}").unwrap();
|
||||||
|
|
|
@ -295,15 +295,15 @@ impl AffineMatrixBackground {
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_scale_rotation_position(
|
pub fn from_scale_rotation_position(
|
||||||
transform_origin: Vector2D<Num<i32, 8>>,
|
transform_origin: impl Into<Vector2D<Num<i32, 8>>>,
|
||||||
scale: Vector2D<Num<i32, 8>>,
|
scale: impl Into<Vector2D<Num<i32, 8>>>,
|
||||||
rotation: Num<i32, 16>,
|
rotation: Num<i32, 16>,
|
||||||
position: Vector2D<Num<i32, 8>>,
|
position: impl Into<Vector2D<Num<i32, 8>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::syscall::bg_affine_matrix(
|
crate::syscall::bg_affine_matrix(
|
||||||
transform_origin,
|
transform_origin.into(),
|
||||||
position.try_change_base::<i16, 8>().unwrap().floor(),
|
position.into().try_change_base::<i16, 8>().unwrap().floor(),
|
||||||
scale.try_change_base().unwrap(),
|
scale.into().try_change_base().unwrap(),
|
||||||
rotation.rem_euclid(1.into()).try_change_base().unwrap(),
|
rotation.rem_euclid(1.into()).try_change_base().unwrap(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,12 +78,12 @@ impl Font {
|
||||||
impl Font {
|
impl Font {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
/// Create renderer starting at the given tile co-ordinates.
|
/// Create renderer starting at the given tile co-ordinates.
|
||||||
pub fn render_text(&self, tile_pos: Vector2D<u16>) -> TextRenderer<'_> {
|
pub fn render_text(&self, tile_pos: impl Into<Vector2D<u16>>) -> TextRenderer<'_> {
|
||||||
TextRenderer {
|
TextRenderer {
|
||||||
current_x_pos: 0,
|
current_x_pos: 0,
|
||||||
current_y_pos: 0,
|
current_y_pos: 0,
|
||||||
font: self,
|
font: self,
|
||||||
tile_pos,
|
tile_pos: tile_pos.into(),
|
||||||
tiles: Default::default(),
|
tiles: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,7 +228,7 @@ impl<'a, 'b> TextRenderer<'b> {
|
||||||
for ((x, y), tile) in self.tiles.iter() {
|
for ((x, y), tile) in self.tiles.iter() {
|
||||||
bg.set_tile(
|
bg.set_tile(
|
||||||
vram_manager,
|
vram_manager,
|
||||||
(self.tile_pos.x + *x as u16, self.tile_pos.y + *y as u16).into(),
|
(self.tile_pos.x + *x as u16, self.tile_pos.y + *y as u16),
|
||||||
&tile.tile_set(),
|
&tile.tile_set(),
|
||||||
tile.tile_setting(),
|
tile.tile_setting(),
|
||||||
);
|
);
|
||||||
|
@ -292,7 +292,7 @@ mod tests {
|
||||||
for x in 0..30u16 {
|
for x in 0..30u16 {
|
||||||
bg.set_tile(
|
bg.set_tile(
|
||||||
&mut vram,
|
&mut vram,
|
||||||
(x, y).into(),
|
(x, y),
|
||||||
&background_tile.tile_set(),
|
&background_tile.tile_set(),
|
||||||
background_tile.tile_setting(),
|
background_tile.tile_setting(),
|
||||||
);
|
);
|
||||||
|
@ -301,7 +301,7 @@ mod tests {
|
||||||
|
|
||||||
vram.remove_dynamic_tile(background_tile);
|
vram.remove_dynamic_tile(background_tile);
|
||||||
|
|
||||||
let mut renderer = FONT.render_text((0u16, 3u16).into());
|
let mut renderer = FONT.render_text((0u16, 3u16));
|
||||||
|
|
||||||
// Test twice to ensure that clearing works
|
// Test twice to ensure that clearing works
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
|
|
|
@ -229,11 +229,11 @@ impl BufferedRender<'_> {
|
||||||
/// let mut writer = ObjectTextRender::new(&EXAMPLE_FONT, Size::S16x16, palette);
|
/// let mut writer = ObjectTextRender::new(&EXAMPLE_FONT, Size::S16x16, palette);
|
||||||
///
|
///
|
||||||
/// let _ = writeln!(writer, "Hello, World!");
|
/// let _ = writeln!(writer, "Hello, World!");
|
||||||
/// writer.layout((WIDTH, 40).into(), TextAlignment::Left, 2);
|
/// writer.layout((WIDTH, 40), TextAlignment::Left, 2);
|
||||||
///
|
///
|
||||||
/// loop {
|
/// loop {
|
||||||
/// writer.next_letter_group();
|
/// writer.next_letter_group();
|
||||||
/// writer.update((0, 0).into());
|
/// writer.update((0, 0));
|
||||||
/// vblank.wait_for_vblank();
|
/// vblank.wait_for_vblank();
|
||||||
/// let oam = &mut unmanaged.iter();
|
/// let oam = &mut unmanaged.iter();
|
||||||
/// writer.commit(oam);
|
/// writer.commit(oam);
|
||||||
|
@ -287,7 +287,7 @@ impl ObjectTextRender<'_> {
|
||||||
/// Force a relayout, must be called after writing.
|
/// Force a relayout, must be called after writing.
|
||||||
pub fn layout(
|
pub fn layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
area: Vector2D<i32>,
|
area: impl Into<Vector2D<i32>>,
|
||||||
alignment: TextAlignment,
|
alignment: TextAlignment,
|
||||||
paragraph_spacing: i32,
|
paragraph_spacing: i32,
|
||||||
) {
|
) {
|
||||||
|
@ -295,7 +295,7 @@ impl ObjectTextRender<'_> {
|
||||||
self.buffer.font,
|
self.buffer.font,
|
||||||
&self.buffer.preprocessor,
|
&self.buffer.preprocessor,
|
||||||
&LayoutSettings {
|
&LayoutSettings {
|
||||||
area,
|
area: area.into(),
|
||||||
alignment,
|
alignment,
|
||||||
paragraph_spacing,
|
paragraph_spacing,
|
||||||
},
|
},
|
||||||
|
@ -331,7 +331,7 @@ impl ObjectTextRender<'_> {
|
||||||
/// Updates the internal state of the number of letters to write and popped
|
/// Updates the internal state of the number of letters to write and popped
|
||||||
/// line. Should be called in the same frame as and after
|
/// line. Should be called in the same frame as and after
|
||||||
/// [`next_letter_group`][ObjectTextRender::next_letter_group], [`next_line`][ObjectTextRender::next_line], and [`pop_line`][ObjectTextRender::pop_line].
|
/// [`next_letter_group`][ObjectTextRender::next_letter_group], [`next_line`][ObjectTextRender::next_line], and [`pop_line`][ObjectTextRender::pop_line].
|
||||||
pub fn update(&mut self, position: Vector2D<i32>) {
|
pub fn update(&mut self, position: impl Into<Vector2D<i32>>) {
|
||||||
if !self.buffer.buffered_chars.is_empty()
|
if !self.buffer.buffered_chars.is_empty()
|
||||||
&& self.buffer.letters.letters.len() <= self.number_of_objects + 5
|
&& self.buffer.letters.letters.len() <= self.number_of_objects + 5
|
||||||
{
|
{
|
||||||
|
@ -339,7 +339,7 @@ impl ObjectTextRender<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.layout.update_objects_to_display_at_position(
|
self.layout.update_objects_to_display_at_position(
|
||||||
position,
|
position.into(),
|
||||||
self.buffer.letters.letters.iter(),
|
self.buffer.letters.letters.iter(),
|
||||||
self.number_of_objects,
|
self.number_of_objects,
|
||||||
);
|
);
|
||||||
|
|
|
@ -489,9 +489,9 @@ impl Object<'_> {
|
||||||
|
|
||||||
/// Sets the position of the object.
|
/// Sets the position of the object.
|
||||||
/// Use [position](Self::position) to get the value
|
/// Use [position](Self::position) to get the value
|
||||||
pub fn set_position(&mut self, position: Vector2D<i32>) -> &mut Self {
|
pub fn set_position(&mut self, position: impl Into<Vector2D<i32>>) -> &mut Self {
|
||||||
// safety: only have one of these, doesn't modify slotmap
|
// safety: only have one of these, doesn't modify slotmap
|
||||||
unsafe { self.object().set_position(position) };
|
unsafe { self.object().set_position(position.into()) };
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,8 +258,7 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
|
|
||||||
let offset = self.current_pos - (x_start * 8, y_start * 8).into();
|
let offset = self.current_pos - (x_start * 8, y_start * 8).into();
|
||||||
|
|
||||||
self.map
|
self.map.set_scroll_pos((offset.x as i16, offset.y as i16));
|
||||||
.set_scroll_pos((offset.x as i16, offset.y as i16).into());
|
|
||||||
self.offset = (x_start, y_start).into();
|
self.offset = (x_start, y_start).into();
|
||||||
|
|
||||||
let copy_from = self.copied_up_to;
|
let copy_from = self.copied_up_to;
|
||||||
|
@ -274,7 +273,7 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
|
|
||||||
self.map.set_tile(
|
self.map.set_tile(
|
||||||
vram,
|
vram,
|
||||||
(x_idx as u16, (y_idx + copy_from as usize) as u16).into(),
|
(x_idx as u16, (y_idx + copy_from as usize) as u16),
|
||||||
tileset,
|
tileset,
|
||||||
tile_setting,
|
tile_setting,
|
||||||
);
|
);
|
||||||
|
@ -374,8 +373,7 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
(
|
(
|
||||||
size.tile_pos_x(tile_x - self.offset.x),
|
size.tile_pos_x(tile_x - self.offset.x),
|
||||||
size.tile_pos_y(tile_y - self.offset.y),
|
size.tile_pos_y(tile_y - self.offset.y),
|
||||||
)
|
),
|
||||||
.into(),
|
|
||||||
tileset,
|
tileset,
|
||||||
tile_setting,
|
tile_setting,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::bitarray::Bitarray;
|
||||||
use crate::display::affine::AffineMatrixBackground;
|
use crate::display::affine::AffineMatrixBackground;
|
||||||
use crate::display::tile_data::TileData;
|
use crate::display::tile_data::TileData;
|
||||||
use crate::display::{Priority, DISPLAY_CONTROL};
|
use crate::display::{Priority, DISPLAY_CONTROL};
|
||||||
use crate::dma::dma_copy16;
|
use crate::dma;
|
||||||
use crate::fixnum::Vector2D;
|
use crate::fixnum::Vector2D;
|
||||||
use crate::memory_mapped::MemoryMapped;
|
use crate::memory_mapped::MemoryMapped;
|
||||||
|
|
||||||
|
@ -93,9 +93,8 @@ where
|
||||||
|
|
||||||
if *self.tiles_dirty() {
|
if *self.tiles_dirty() {
|
||||||
unsafe {
|
unsafe {
|
||||||
dma_copy16(
|
screenblock_memory.copy_from(
|
||||||
self.tiles_mut().as_ptr() as *const u16,
|
self.tiles_mut().as_ptr() as *const u16,
|
||||||
screenblock_memory,
|
|
||||||
self.map_size().num_tiles(),
|
self.map_size().num_tiles(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -226,7 +225,7 @@ impl RegularMap {
|
||||||
pub fn set_tile(
|
pub fn set_tile(
|
||||||
&mut self,
|
&mut self,
|
||||||
vram: &mut VRamManager,
|
vram: &mut VRamManager,
|
||||||
pos: Vector2D<u16>,
|
pos: impl Into<Vector2D<u16>>,
|
||||||
tileset: &TileSet<'_>,
|
tileset: &TileSet<'_>,
|
||||||
tile_setting: TileSetting,
|
tile_setting: TileSetting,
|
||||||
) {
|
) {
|
||||||
|
@ -238,7 +237,7 @@ impl RegularMap {
|
||||||
self.colours()
|
self.colours()
|
||||||
);
|
);
|
||||||
|
|
||||||
let pos = self.map_size().gba_offset(pos);
|
let pos = self.map_size().gba_offset(pos.into());
|
||||||
self.set_tile_at_pos(vram, pos, tileset, tile_setting);
|
self.set_tile_at_pos(vram, pos, tileset, tile_setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,8 +291,13 @@ impl RegularMap {
|
||||||
self.scroll
|
self.scroll
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_scroll_pos(&mut self, pos: Vector2D<i16>) {
|
pub fn set_scroll_pos(&mut self, pos: impl Into<Vector2D<i16>>) {
|
||||||
self.scroll = pos;
|
self.scroll = pos.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn x_scroll_dma(&self) -> dma::DmaControllable<i16> {
|
||||||
|
dma::DmaControllable::new(self.x_register().as_ptr())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn x_register(&self) -> MemoryMapped<i16> {
|
fn x_register(&self) -> MemoryMapped<i16> {
|
||||||
|
@ -373,11 +377,11 @@ impl AffineMap {
|
||||||
pub fn set_tile(
|
pub fn set_tile(
|
||||||
&mut self,
|
&mut self,
|
||||||
vram: &mut VRamManager,
|
vram: &mut VRamManager,
|
||||||
pos: Vector2D<u16>,
|
pos: impl Into<Vector2D<u16>>,
|
||||||
tileset: &TileSet<'_>,
|
tileset: &TileSet<'_>,
|
||||||
tile_id: u8,
|
tile_id: u8,
|
||||||
) {
|
) {
|
||||||
let pos = self.map_size().gba_offset(pos);
|
let pos = self.map_size().gba_offset(pos.into());
|
||||||
let colours = self.colours();
|
let colours = self.colours();
|
||||||
|
|
||||||
let old_tile = self.tiles_mut()[pos];
|
let old_tile = self.tiles_mut()[pos];
|
||||||
|
|
|
@ -5,7 +5,7 @@ use alloc::{slice, vec::Vec};
|
||||||
use crate::{
|
use crate::{
|
||||||
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
|
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
|
||||||
display::palette16,
|
display::palette16,
|
||||||
dma::dma_copy16,
|
dma,
|
||||||
hash_map::{Entry, HashMap},
|
hash_map::{Entry, HashMap},
|
||||||
memory_mapped::MemoryMapped1DArray,
|
memory_mapped::MemoryMapped1DArray,
|
||||||
};
|
};
|
||||||
|
@ -413,7 +413,9 @@ impl VRamManager {
|
||||||
/// Copies raw palettes to the background palette without any checks.
|
/// Copies raw palettes to the background palette without any checks.
|
||||||
pub fn set_background_palette_raw(&mut self, palette: &[u16]) {
|
pub fn set_background_palette_raw(&mut self, palette: &[u16]) {
|
||||||
unsafe {
|
unsafe {
|
||||||
dma_copy16(palette.as_ptr(), PALETTE_BACKGROUND.as_ptr(), palette.len());
|
PALETTE_BACKGROUND
|
||||||
|
.as_ptr()
|
||||||
|
.copy_from_nonoverlapping(palette.as_ptr(), palette.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,6 +425,36 @@ impl VRamManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The DMA register for controlling a single colour in a single background. Good for drawing gradients
|
||||||
|
#[must_use]
|
||||||
|
pub fn background_palette_colour_dma(
|
||||||
|
&self,
|
||||||
|
pal_index: usize,
|
||||||
|
colour_index: usize,
|
||||||
|
) -> dma::DmaControllable<u16> {
|
||||||
|
assert!(pal_index < 16);
|
||||||
|
assert!(colour_index < 16);
|
||||||
|
|
||||||
|
dma::DmaControllable::new(unsafe {
|
||||||
|
PALETTE_BACKGROUND
|
||||||
|
.as_ptr()
|
||||||
|
.add(16 * pal_index + colour_index)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a single colour for a given background palette. Takes effect immediately
|
||||||
|
pub fn set_background_palette_colour(
|
||||||
|
&mut self,
|
||||||
|
pal_index: usize,
|
||||||
|
colour_index: usize,
|
||||||
|
colour: u16,
|
||||||
|
) {
|
||||||
|
assert!(pal_index < 16);
|
||||||
|
assert!(colour_index < 16);
|
||||||
|
|
||||||
|
PALETTE_BACKGROUND.set(colour_index + 16 * pal_index, colour);
|
||||||
|
}
|
||||||
|
|
||||||
/// Copies palettes to the background palettes without any checks.
|
/// Copies palettes to the background palettes without any checks.
|
||||||
pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) {
|
pub fn set_background_palettes(&mut self, palettes: &[palette16::Palette16]) {
|
||||||
for (palette_index, entry) in palettes.iter().enumerate() {
|
for (palette_index, entry) in palettes.iter().enumerate() {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//! The window feature of the GBA.
|
//! The window feature of the GBA.
|
||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{fixnum::Rect, memory_mapped::MemoryMapped};
|
use crate::{dma, fixnum::Rect, memory_mapped::MemoryMapped};
|
||||||
|
|
||||||
use super::{tiled::BackgroundID, DISPLAY_CONTROL, HEIGHT, WIDTH};
|
use super::{tiled::BackgroundID, DISPLAY_CONTROL, HEIGHT, WIDTH};
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ pub enum WinIn {
|
||||||
impl<'gba> Windows<'gba> {
|
impl<'gba> Windows<'gba> {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
let s = Self {
|
let s = Self {
|
||||||
wins: [MovableWindow::new(), MovableWindow::new()],
|
wins: [MovableWindow::new(0), MovableWindow::new(1)],
|
||||||
out: Window::new(),
|
out: Window::new(),
|
||||||
obj: Window::new(),
|
obj: Window::new(),
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
|
@ -63,8 +63,8 @@ impl<'gba> Windows<'gba> {
|
||||||
/// modify them. This should be done during vblank shortly after the wait
|
/// modify them. This should be done during vblank shortly after the wait
|
||||||
/// for next vblank call.
|
/// for next vblank call.
|
||||||
pub fn commit(&self) {
|
pub fn commit(&self) {
|
||||||
for (id, win) in self.wins.iter().enumerate() {
|
for win in &self.wins {
|
||||||
win.commit(id);
|
win.commit();
|
||||||
}
|
}
|
||||||
self.out.commit(2);
|
self.out.commit(2);
|
||||||
self.obj.commit(3);
|
self.obj.commit(3);
|
||||||
|
@ -85,6 +85,7 @@ pub struct Window {
|
||||||
pub struct MovableWindow {
|
pub struct MovableWindow {
|
||||||
inner: Window,
|
inner: Window,
|
||||||
rect: Rect<u8>,
|
rect: Rect<u8>,
|
||||||
|
id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
@ -166,10 +167,11 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MovableWindow {
|
impl MovableWindow {
|
||||||
fn new() -> Self {
|
fn new(id: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Window::new(),
|
inner: Window::new(),
|
||||||
rect: Rect::new((0, 0).into(), (0, 0).into()),
|
rect: Rect::new((0, 0).into(), (0, 0).into()),
|
||||||
|
id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +203,7 @@ impl MovableWindow {
|
||||||
/// nothing rendered and represents a 0x0 rectangle at (0, 0).
|
/// nothing rendered and represents a 0x0 rectangle at (0, 0).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn reset(&mut self) -> &mut Self {
|
pub fn reset(&mut self) -> &mut Self {
|
||||||
*self = Self::new();
|
*self = Self::new(self.id);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -227,8 +229,8 @@ impl MovableWindow {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commit(&self, id: usize) {
|
fn commit(&self) {
|
||||||
self.inner.commit(id);
|
self.inner.commit(self.id);
|
||||||
|
|
||||||
let left_right =
|
let left_right =
|
||||||
(self.rect.position.x as u16) << 8 | (self.rect.position.x + self.rect.size.x) as u16;
|
(self.rect.position.x as u16) << 8 | (self.rect.position.x + self.rect.size.x) as u16;
|
||||||
|
@ -236,8 +238,8 @@ impl MovableWindow {
|
||||||
let top_bottom =
|
let top_bottom =
|
||||||
(self.rect.position.y as u16) << 8 | (self.rect.position.y + self.rect.size.y) as u16;
|
(self.rect.position.y as u16) << 8 | (self.rect.position.y + self.rect.size.y) as u16;
|
||||||
unsafe {
|
unsafe {
|
||||||
REG_HORIZONTAL_BASE.add(id).write_volatile(left_right);
|
REG_HORIZONTAL_BASE.add(self.id).write_volatile(left_right);
|
||||||
REG_VERTICAL_BASE.add(id).write_volatile(top_bottom);
|
REG_VERTICAL_BASE.add(self.id).write_volatile(top_bottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,4 +266,14 @@ impl MovableWindow {
|
||||||
);
|
);
|
||||||
self.set_position_u8(new_rect)
|
self.set_position_u8(new_rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// DMA to control the horizontal position of the window. The lower 8 bits are
|
||||||
|
/// the left hand side, and the upper 8 bits are the right hand side.
|
||||||
|
///
|
||||||
|
/// When you use this, you should also set the height of the window approprately using
|
||||||
|
/// [`set_position`](Self::set_position).
|
||||||
|
#[must_use]
|
||||||
|
pub fn horizontal_position_dma(&self) -> dma::DmaControllable<u16> {
|
||||||
|
dma::DmaControllable::new(unsafe { REG_HORIZONTAL_BASE.add(self.id) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
139
agb/src/dma.rs
139
agb/src/dma.rs
|
@ -1,5 +1,144 @@
|
||||||
|
use core::{marker::PhantomData, mem::size_of, pin::Pin};
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
use crate::memory_mapped::MemoryMapped;
|
use crate::memory_mapped::MemoryMapped;
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct DmaController {}
|
||||||
|
|
||||||
|
impl DmaController {
|
||||||
|
pub(crate) const fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dma(&mut self) -> Dmas<'_> {
|
||||||
|
unsafe { Dmas::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dmas<'gba> {
|
||||||
|
phantom: PhantomData<&'gba ()>,
|
||||||
|
|
||||||
|
pub dma0: Dma,
|
||||||
|
pub dma3: Dma,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gba> Dmas<'gba> {
|
||||||
|
unsafe fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
phantom: PhantomData,
|
||||||
|
|
||||||
|
dma0: Dma::new(0),
|
||||||
|
dma3: Dma::new(3),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dma {
|
||||||
|
number: usize,
|
||||||
|
|
||||||
|
source_addr: MemoryMapped<u32>,
|
||||||
|
dest_addr: MemoryMapped<u32>,
|
||||||
|
ctrl_addr: MemoryMapped<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dma {
|
||||||
|
unsafe fn new(number: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
number,
|
||||||
|
source_addr: unsafe { MemoryMapped::new(dma_source_addr(number)) },
|
||||||
|
dest_addr: unsafe { MemoryMapped::new(dma_dest_addr(number)) },
|
||||||
|
ctrl_addr: unsafe { MemoryMapped::new(dma_control_addr(number)) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disable(&mut self) {
|
||||||
|
unsafe { MemoryMapped::new(dma_control_addr(self.number)) }.set(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe fn hblank_transfer<'a, T>(
|
||||||
|
&'a self,
|
||||||
|
location: &DmaControllable<T>,
|
||||||
|
values: &'a [T],
|
||||||
|
) -> DmaTransferHandle<'a, T>
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
assert!(
|
||||||
|
values.len() >= 160,
|
||||||
|
"need to pass at least 160 values for a hblank_transfer"
|
||||||
|
);
|
||||||
|
let handle = unsafe { DmaTransferHandle::new(self.number, values) };
|
||||||
|
|
||||||
|
let n_transfers = (size_of::<T>() / 2) as u32;
|
||||||
|
|
||||||
|
self.source_addr.set(handle.data.as_ptr() as u32);
|
||||||
|
self.dest_addr.set(location.memory_location as u32);
|
||||||
|
|
||||||
|
self.ctrl_addr.set(
|
||||||
|
(0b10 << 0x15) | // keep destination address fixed
|
||||||
|
// (0b00 << 0x17) | // increment the source address each time
|
||||||
|
1 << 0x19 | // repeat the copy each hblank
|
||||||
|
// 0 << 0x1a | // copy in half words (see n_transfers above)
|
||||||
|
0b10 << 0x1c | // copy each hblank
|
||||||
|
1 << 0x1f | // enable the dma
|
||||||
|
n_transfers, // the number of halfwords to copy
|
||||||
|
);
|
||||||
|
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct to describe things you can modify using DMA (normally some register within the GBA)
|
||||||
|
///
|
||||||
|
/// This is generally used to perform fancy graphics tricks like screen wobble on a per-scanline basis or
|
||||||
|
/// to be able to create a track like in mario kart. This is an advanced technique and likely not needed
|
||||||
|
/// unless you want to do fancy graphics.
|
||||||
|
pub struct DmaControllable<Item> {
|
||||||
|
memory_location: *mut Item,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Item> DmaControllable<Item> {
|
||||||
|
pub(crate) fn new(memory_location: *mut Item) -> Self {
|
||||||
|
Self { memory_location }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DmaTransferHandle<'a, T>
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
number: usize,
|
||||||
|
data: Pin<Box<[T]>>,
|
||||||
|
|
||||||
|
phantom: PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> DmaTransferHandle<'a, T>
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
pub(crate) unsafe fn new(number: usize, data: &[T]) -> Self {
|
||||||
|
Self {
|
||||||
|
number,
|
||||||
|
data: Box::into_pin(data.into()),
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Drop for DmaTransferHandle<'a, T>
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
Dma::new(self.number).disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fn dma_source_addr(dma: usize) -> usize {
|
const fn dma_source_addr(dma: usize) -> usize {
|
||||||
0x0400_00b0 + 0x0c * dma
|
0x0400_00b0 + 0x0c * dma
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
/// for x in 0..30u16 {
|
/// for x in 0..30u16 {
|
||||||
/// bg.set_tile(
|
/// bg.set_tile(
|
||||||
/// &mut vram,
|
/// &mut vram,
|
||||||
/// (x, y).into(),
|
/// (x, y),
|
||||||
/// &tileset,
|
/// &tileset,
|
||||||
/// water_tiles::tiles.tile_settings[0],
|
/// water_tiles::tiles.tile_settings[0],
|
||||||
/// );
|
/// );
|
||||||
|
@ -235,6 +235,8 @@ pub struct Gba {
|
||||||
pub save: save::SaveManager,
|
pub save: save::SaveManager,
|
||||||
/// Manages access to the Game Boy Advance's 4 timers.
|
/// Manages access to the Game Boy Advance's 4 timers.
|
||||||
pub timers: timer::TimerController,
|
pub timers: timer::TimerController,
|
||||||
|
/// Manages access to the Game Boy Advance's DMA
|
||||||
|
pub dma: dma::DmaController,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gba {
|
impl Gba {
|
||||||
|
@ -255,6 +257,7 @@ impl Gba {
|
||||||
mixer: sound::mixer::MixerController::new(),
|
mixer: sound::mixer::MixerController::new(),
|
||||||
save: save::SaveManager::new(),
|
save: save::SaveManager::new(),
|
||||||
timers: timer::TimerController::new(),
|
timers: timer::TimerController::new(),
|
||||||
|
dma: dma::DmaController::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,10 @@ impl<T> MemoryMapped<T> {
|
||||||
unsafe { self.address.write_volatile(val) }
|
unsafe { self.address.write_volatile(val) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_ptr(&self) -> *mut T {
|
||||||
|
self.address
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> MemoryMapped<T>
|
impl<T> MemoryMapped<T>
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub(crate) fn load_help_text(
|
||||||
|
|
||||||
background.set_tile(
|
background.set_tile(
|
||||||
vram,
|
vram,
|
||||||
(x + at_tile.0, at_tile.1).into(),
|
(x + at_tile.0, at_tile.1),
|
||||||
&help_tiledata.tiles,
|
&help_tiledata.tiles,
|
||||||
help_tiledata.tile_settings[tile_id as usize],
|
help_tiledata.tile_settings[tile_id as usize],
|
||||||
)
|
)
|
||||||
|
@ -53,7 +53,7 @@ pub(crate) fn load_description(
|
||||||
let tile_id = y * 8 + x + 8 * 11 * (face_id as u16 % 10);
|
let tile_id = y * 8 + x + 8 * 11 * (face_id as u16 % 10);
|
||||||
descriptions_map.set_tile(
|
descriptions_map.set_tile(
|
||||||
vram,
|
vram,
|
||||||
(x, y).into(),
|
(x, y),
|
||||||
&description_data.tiles,
|
&description_data.tiles,
|
||||||
description_data.tile_settings[tile_id as usize],
|
description_data.tile_settings[tile_id as usize],
|
||||||
)
|
)
|
||||||
|
@ -74,15 +74,15 @@ fn create_background_map(map: &mut RegularMap, vram: &mut VRamManager, stars_til
|
||||||
backgrounds::stars.tile_settings[tile_id as usize]
|
backgrounds::stars.tile_settings[tile_id as usize]
|
||||||
};
|
};
|
||||||
|
|
||||||
map.set_tile(vram, (x, y).into(), stars_tileset, tile_setting);
|
map.set_tile(vram, (x, y), stars_tileset, tile_setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
map.set_scroll_pos((0i16, rng::gen().rem_euclid(8) as i16).into());
|
map.set_scroll_pos((0i16, rng::gen().rem_euclid(8) as i16));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_title_screen(background: &mut RegularMap, vram: &mut VRamManager, sfx: &mut Sfx) {
|
pub fn show_title_screen(background: &mut RegularMap, vram: &mut VRamManager, sfx: &mut Sfx) {
|
||||||
background.set_scroll_pos((0i16, 0).into());
|
background.set_scroll_pos((0i16, 0));
|
||||||
vram.set_background_palettes(backgrounds::PALETTES);
|
vram.set_background_palettes(backgrounds::PALETTES);
|
||||||
|
|
||||||
background.set_visible(false);
|
background.set_visible(false);
|
||||||
|
|
|
@ -489,7 +489,7 @@ pub(crate) fn battle_screen(
|
||||||
agb.sfx.battle();
|
agb.sfx.battle();
|
||||||
agb.sfx.frame();
|
agb.sfx.frame();
|
||||||
|
|
||||||
help_background.set_scroll_pos((-16i16, -97i16).into());
|
help_background.set_scroll_pos((-16i16, -97i16));
|
||||||
crate::background::load_help_text(&mut agb.vram, help_background, 1, (0, 0));
|
crate::background::load_help_text(&mut agb.vram, help_background, 1, (0, 0));
|
||||||
crate::background::load_help_text(&mut agb.vram, help_background, 2, (0, 1));
|
crate::background::load_help_text(&mut agb.vram, help_background, 2, (0, 1));
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use agb::display::object::{OamManaged, Object};
|
use agb::display::object::{OamManaged, Object};
|
||||||
|
use agb::fixnum::Vector2D;
|
||||||
use agb::rng;
|
use agb::rng;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
@ -149,7 +150,7 @@ impl<'a> BattleScreenDisplay<'a> {
|
||||||
let mut attack_obj = obj
|
let mut attack_obj = obj
|
||||||
.object_sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack));
|
.object_sprite(ENEMY_ATTACK_SPRITES.sprite_for_attack(EnemyAttackType::Attack));
|
||||||
|
|
||||||
let attack_obj_position = (120, 56 + 32 * i).into();
|
let attack_obj_position = Vector2D::new(120, 56 + 32 * i);
|
||||||
attack_obj.set_position(attack_obj_position).hide();
|
attack_obj.set_position(attack_obj_position).hide();
|
||||||
|
|
||||||
let mut attack_cooldown =
|
let mut attack_cooldown =
|
||||||
|
|
|
@ -163,9 +163,9 @@ pub(crate) fn customise_screen(
|
||||||
) -> PlayerDice {
|
) -> PlayerDice {
|
||||||
agb.sfx.customise();
|
agb.sfx.customise();
|
||||||
agb.sfx.frame();
|
agb.sfx.frame();
|
||||||
descriptions_map.set_scroll_pos((-174i16, -52).into());
|
descriptions_map.set_scroll_pos((-174i16, -52));
|
||||||
|
|
||||||
help_background.set_scroll_pos((-148i16, -34).into());
|
help_background.set_scroll_pos((-148i16, -34));
|
||||||
crate::background::load_help_text(&mut agb.vram, help_background, 0, (0, 0));
|
crate::background::load_help_text(&mut agb.vram, help_background, 0, (0, 0));
|
||||||
|
|
||||||
// create the dice
|
// create the dice
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub fn load_ui(map: &mut RegularMap, vram_manager: &mut VRamManager) {
|
||||||
let tile_pos = y * 30 + x;
|
let tile_pos = y * 30 + x;
|
||||||
let tile_setting = tilemaps::UI_BACKGROUND_MAP[tile_pos as usize];
|
let tile_setting = tilemaps::UI_BACKGROUND_MAP[tile_pos as usize];
|
||||||
|
|
||||||
map.set_tile(vram_manager, (x, y).into(), &ui_tileset, tile_setting);
|
map.set_tile(vram_manager, (x, y), &ui_tileset, tile_setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ pub fn load_level_background(
|
||||||
let tile_pos = y * 22 + x;
|
let tile_pos = y * 22 + x;
|
||||||
let tile_setting = level_map[tile_pos as usize];
|
let tile_setting = level_map[tile_pos as usize];
|
||||||
|
|
||||||
map.set_tile(vram_manager, (x, y).into(), &level_tileset, tile_setting);
|
map.set_tile(vram_manager, (x, y), &level_tileset, tile_setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ pub fn write_level(
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
map.set_tile(vram, (i as u16, 0).into(), tileset, tile_settings[tile]);
|
map.set_tile(vram, (i as u16, 0), tileset, tile_settings[tile]);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.set_scroll_pos((-(WIDTH / 2 - 7 * 8 / 2) as i16, -(HEIGHT / 2 - 4) as i16).into());
|
map.set_scroll_pos((-(WIDTH / 2 - 7 * 8 / 2) as i16, -(HEIGHT / 2 - 4) as i16));
|
||||||
}
|
}
|
||||||
|
|
|
@ -794,7 +794,7 @@ pub fn main(mut agb: agb::Gba) -> ! {
|
||||||
for x in 0..32u16 {
|
for x in 0..32u16 {
|
||||||
world_display.set_tile(
|
world_display.set_tile(
|
||||||
&mut vram,
|
&mut vram,
|
||||||
(x, y).into(),
|
(x, y),
|
||||||
&tileset,
|
&tileset,
|
||||||
tile_sheet::background.tile_settings[level_display::BLANK],
|
tile_sheet::background.tile_settings[level_display::BLANK],
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub fn show_splash_screen(
|
||||||
map: &mut RegularMap,
|
map: &mut RegularMap,
|
||||||
vram: &mut VRamManager,
|
vram: &mut VRamManager,
|
||||||
) {
|
) {
|
||||||
map.set_scroll_pos((0i16, 0i16).into());
|
map.set_scroll_pos((0i16, 0i16));
|
||||||
let tile_data = match which {
|
let tile_data = match which {
|
||||||
SplashScreen::Start => splash_screens::splash,
|
SplashScreen::Start => splash_screens::splash,
|
||||||
SplashScreen::End => splash_screens::thanks_for_playing,
|
SplashScreen::End => splash_screens::thanks_for_playing,
|
||||||
|
|
Loading…
Reference in a new issue