mirror of
https://github.com/italicsjenga/agb.git
synced 2024-12-23 16:21:33 +11:00
Merge remote-tracking branch 'upstream/master' into object-controller2
This commit is contained in:
commit
f17084d4f6
2
.vscode/agb.code-workspace
vendored
2
.vscode/agb.code-workspace
vendored
|
@ -40,4 +40,4 @@
|
||||||
"path": "../tools"
|
"path": "../tools"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Added a new agb::sync module that contains GBA-specific synchronization primitives.
|
- Added a new agb::sync module that contains GBA-specific synchronization primitives.
|
||||||
- Added support for save files.
|
- Added support for save files.
|
||||||
- Added implementation of `HashMap.retain()`.
|
- Added implementation of `HashMap.retain()`.
|
||||||
|
- Added support for affine backgrounds (tiled modes 1 and 2) which allows for scaling, rotating etc of tiled backgrounds.
|
||||||
|
- Added support for 256 colour backgrounds (when working with affine ones).
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Many of the places that originally disabled IRQs now use the `sync` module, reducing the chance of missed interrupts.
|
- Many of the places that originally disabled IRQs now use the `sync` module, reducing the chance of missed interrupts.
|
||||||
- HashMap iterators now implement `size_hint` which should result in slightly better generation of code using those iterators.
|
- HashMap iterators now implement `size_hint` which should result in slightly better generation of code using those iterators.
|
||||||
|
- Transparency of backgrounds is now set once in the toml file rather than once for every image.
|
||||||
|
- Palette generation now takes into account every single background a toml definition rather than one at a time, you can now find it in the PALETTES constant rather than in every individual image.
|
||||||
|
- Sound frequency is no longer a crate feature, instead set when initialising the sound mixer.
|
||||||
|
|
||||||
### 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).
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
## Rust for the Game Boy Advance
|
## Rust for the Game Boy Advance
|
||||||
|
|
||||||
|
[![Docs](https://docs.rs/agb/badge.svg)](https://docs.rs/agb/latest/agb)
|
||||||
|
[![Build](https://github.com/agbrs/agb/actions/workflows/build-and-test.yml/badge.svg?branch=master)](https://github.com/agbrs/agb/actions/workflows/build-and-test.yml)
|
||||||
|
[![Licence](https://img.shields.io/crates/l/agb)](https://www.mozilla.org/en-US/MPL/2.0/)
|
||||||
|
[![Crates.io](https://img.shields.io/crates/v/agb)](https://crates.io/crates/agb)
|
||||||
|
|
||||||
![AGB logo](.github/logo.png)
|
![AGB logo](.github/logo.png)
|
||||||
|
|
||||||
This is a library for making games on the Game Boy Advance using the Rust
|
This is a library for making games on the Game Boy Advance using the Rust
|
||||||
|
|
|
@ -570,7 +570,8 @@ impl<I: FixedWidthUnsignedInteger, const N: usize> Debug for Num<I, N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A vector of two points: (x, y) represened by integers or fixed point numbers
|
/// A vector of two points: (x, y) represened by integers or fixed point numbers
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct Vector2D<T: Number> {
|
pub struct Vector2D<T: Number> {
|
||||||
/// The x coordinate
|
/// The x coordinate
|
||||||
pub x: T,
|
pub x: T,
|
||||||
|
@ -1015,6 +1016,14 @@ impl<T: Number> Vector2D<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Number + Neg<Output = T>> Neg for Vector2D<T> {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
(-self.x, -self.y).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
@ -1218,6 +1227,15 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_only_frac_bits() {
|
||||||
|
let quarter: Num<u8, 8> = num!(0.25);
|
||||||
|
let neg_quarter: Num<i16, 15> = num!(-0.25);
|
||||||
|
|
||||||
|
assert_eq!(quarter + quarter, num!(0.5));
|
||||||
|
assert_eq!(neg_quarter + neg_quarter, num!(-0.5));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_vector_multiplication_and_division() {
|
fn test_vector_multiplication_and_division() {
|
||||||
let a: Vector2D<i32> = (1, 2).into();
|
let a: Vector2D<i32> = (1, 2).into();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct Colour {
|
pub struct Colour {
|
||||||
pub r: u8,
|
pub r: u8,
|
||||||
|
@ -20,3 +22,22 @@ impl Colour {
|
||||||
self.a != 255
|
self.a != 255
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for Colour {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(colour: &str) -> Result<Self, Self::Err> {
|
||||||
|
if colour.len() != 6 {
|
||||||
|
return Err(format!(
|
||||||
|
"Expected colour to be 6 characters, got {}",
|
||||||
|
colour
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = u8::from_str_radix(&colour[0..2], 16).unwrap();
|
||||||
|
let g = u8::from_str_radix(&colour[2..4], 16).unwrap();
|
||||||
|
let b = u8::from_str_radix(&colour[4..6], 16).unwrap();
|
||||||
|
|
||||||
|
Ok(Colour::from_rgb(r, g, b, 255))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use crate::{Colour, TileSize};
|
use crate::{Colour, Colours, TileSize};
|
||||||
|
|
||||||
pub(crate) fn parse(filename: &str) -> Box<dyn Config> {
|
pub(crate) fn parse(filename: &str) -> Box<dyn Config> {
|
||||||
let config_toml =
|
let config_toml =
|
||||||
|
@ -23,18 +23,20 @@ pub(crate) fn parse(filename: &str) -> Box<dyn Config> {
|
||||||
pub(crate) trait Config {
|
pub(crate) trait Config {
|
||||||
fn crate_prefix(&self) -> String;
|
fn crate_prefix(&self) -> String;
|
||||||
fn images(&self) -> HashMap<String, &dyn Image>;
|
fn images(&self) -> HashMap<String, &dyn Image>;
|
||||||
|
fn transparent_colour(&self) -> Option<Colour>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait Image {
|
pub(crate) trait Image {
|
||||||
fn filename(&self) -> String;
|
fn filename(&self) -> String;
|
||||||
fn transparent_colour(&self) -> Option<Colour>;
|
|
||||||
fn tilesize(&self) -> TileSize;
|
fn tilesize(&self) -> TileSize;
|
||||||
|
fn colours(&self) -> Colours;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ConfigV1 {
|
pub struct ConfigV1 {
|
||||||
version: String,
|
version: String,
|
||||||
crate_prefix: Option<String>,
|
crate_prefix: Option<String>,
|
||||||
|
transparent_colour: Option<String>,
|
||||||
|
|
||||||
image: HashMap<String, ImageV1>,
|
image: HashMap<String, ImageV1>,
|
||||||
}
|
}
|
||||||
|
@ -52,6 +54,21 @@ impl Config for ConfigV1 {
|
||||||
.map(|(filename, image)| (filename.clone(), image as &dyn Image))
|
.map(|(filename, image)| (filename.clone(), image as &dyn Image))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn transparent_colour(&self) -> Option<Colour> {
|
||||||
|
if let Some(colour) = &self
|
||||||
|
.transparent_colour
|
||||||
|
.as_ref()
|
||||||
|
.map(|colour| colour.parse().unwrap())
|
||||||
|
{
|
||||||
|
return Some(*colour);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.image
|
||||||
|
.values()
|
||||||
|
.flat_map(|image| image.transparent_colour())
|
||||||
|
.next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -59,6 +76,7 @@ pub struct ImageV1 {
|
||||||
filename: String,
|
filename: String,
|
||||||
transparent_colour: Option<String>,
|
transparent_colour: Option<String>,
|
||||||
tile_size: TileSizeV1,
|
tile_size: TileSizeV1,
|
||||||
|
colours: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image for ImageV1 {
|
impl Image for ImageV1 {
|
||||||
|
@ -66,25 +84,25 @@ impl Image for ImageV1 {
|
||||||
self.filename.clone()
|
self.filename.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transparent_colour(&self) -> Option<Colour> {
|
|
||||||
if let Some(colour) = &self.transparent_colour {
|
|
||||||
if colour.len() != 6 {
|
|
||||||
panic!("Expected colour to be 6 characters, got {}", colour);
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = u8::from_str_radix(&colour[0..2], 16).unwrap();
|
|
||||||
let g = u8::from_str_radix(&colour[2..4], 16).unwrap();
|
|
||||||
let b = u8::from_str_radix(&colour[4..6], 16).unwrap();
|
|
||||||
|
|
||||||
return Some(Colour::from_rgb(r, g, b, 255));
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tilesize(&self) -> TileSize {
|
fn tilesize(&self) -> TileSize {
|
||||||
self.tile_size.into()
|
self.tile_size.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn colours(&self) -> Colours {
|
||||||
|
match self.colours {
|
||||||
|
None | Some(16) => Colours::Colours16,
|
||||||
|
Some(256) => Colours::Colours256,
|
||||||
|
_ => panic!("colours must either not be set or 16 or 256"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageV1 {
|
||||||
|
fn transparent_colour(&self) -> Option<Colour> {
|
||||||
|
self.transparent_colour
|
||||||
|
.as_ref()
|
||||||
|
.map(|colour| colour.parse().unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, Copy)]
|
#[derive(Deserialize, Clone, Copy)]
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use palette16::Palette16OptimisationResults;
|
use palette16::{Palette16OptimisationResults, Palette16Optimiser};
|
||||||
|
use palette256::Palette256;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Literal;
|
use proc_macro2::Literal;
|
||||||
use syn::parse::Parser;
|
use syn::parse::Parser;
|
||||||
use syn::{parse_macro_input, punctuated::Punctuated, LitStr};
|
use syn::{parse_macro_input, punctuated::Punctuated, LitStr};
|
||||||
use syn::{Expr, ExprLit, Lit};
|
use syn::{Expr, ExprLit, Lit};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{iter, path::Path, str};
|
use std::{iter, path::Path, str};
|
||||||
|
|
||||||
|
@ -16,6 +18,7 @@ mod config;
|
||||||
mod font_loader;
|
mod font_loader;
|
||||||
mod image_loader;
|
mod image_loader;
|
||||||
mod palette16;
|
mod palette16;
|
||||||
|
mod palette256;
|
||||||
mod rust_generator;
|
mod rust_generator;
|
||||||
|
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
|
@ -30,6 +33,11 @@ pub(crate) enum TileSize {
|
||||||
Tile32,
|
Tile32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum Colours {
|
||||||
|
Colours16,
|
||||||
|
Colours256,
|
||||||
|
}
|
||||||
|
|
||||||
impl TileSize {
|
impl TileSize {
|
||||||
fn to_size(self) -> usize {
|
fn to_size(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
|
@ -63,14 +71,71 @@ pub fn include_gfx(input: TokenStream) -> TokenStream {
|
||||||
let include_path = path.to_string_lossy();
|
let include_path = path.to_string_lossy();
|
||||||
|
|
||||||
let images = config.images();
|
let images = config.images();
|
||||||
let image_code = images.iter().map(|(image_name, &image)| {
|
|
||||||
convert_image(image, parent, image_name, &config.crate_prefix())
|
let mut optimiser = Palette16Optimiser::new(config.transparent_colour());
|
||||||
});
|
let mut assignment_offsets = HashMap::new();
|
||||||
|
let mut assignment_offset = 0;
|
||||||
|
|
||||||
|
let mut palette256 = Palette256::new();
|
||||||
|
|
||||||
|
for (name, settings) in images.iter() {
|
||||||
|
let image_filename = &parent.join(&settings.filename());
|
||||||
|
let image = Image::load_from_file(image_filename);
|
||||||
|
|
||||||
|
match settings.colours() {
|
||||||
|
Colours::Colours16 => {
|
||||||
|
let tile_size = settings.tilesize().to_size();
|
||||||
|
if image.width % tile_size != 0 || image.height % tile_size != 0 {
|
||||||
|
panic!("Image size not a multiple of tile size");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_to_optimiser(
|
||||||
|
&mut optimiser,
|
||||||
|
&image,
|
||||||
|
tile_size,
|
||||||
|
config.transparent_colour(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let num_tiles = image.width * image.height / settings.tilesize().to_size().pow(2);
|
||||||
|
assignment_offsets.insert(name, assignment_offset);
|
||||||
|
assignment_offset += num_tiles;
|
||||||
|
}
|
||||||
|
Colours::Colours256 => {
|
||||||
|
palette256.add_image(&image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let optimisation_results = optimiser.optimise_palettes();
|
||||||
|
let optimisation_results = palette256.extend_results(&optimisation_results);
|
||||||
|
|
||||||
|
let mut image_code = vec![];
|
||||||
|
|
||||||
|
for (image_name, &image) in images.iter() {
|
||||||
|
let assignment_offset = match image.colours() {
|
||||||
|
Colours::Colours16 => Some(assignment_offsets[image_name]),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
image_code.push(convert_image(
|
||||||
|
image,
|
||||||
|
parent,
|
||||||
|
image_name,
|
||||||
|
&config.crate_prefix(),
|
||||||
|
&optimisation_results,
|
||||||
|
assignment_offset,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let palette_code =
|
||||||
|
rust_generator::generate_palette_code(&optimisation_results, &config.crate_prefix());
|
||||||
|
|
||||||
let module = quote! {
|
let module = quote! {
|
||||||
mod #module_name {
|
mod #module_name {
|
||||||
const _: &[u8] = include_bytes!(#include_path);
|
const _: &[u8] = include_bytes!(#include_path);
|
||||||
|
|
||||||
|
#palette_code
|
||||||
|
|
||||||
#(#image_code)*
|
#(#image_code)*
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -132,7 +197,7 @@ pub fn include_aseprite_inner(input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
let optimised_results = optimiser.optimise_palettes();
|
let optimised_results = optimiser.optimise_palettes();
|
||||||
|
|
||||||
let (palette_data, tile_data, assignments) = palete_tile_data(&optimised_results, &images);
|
let (palette_data, tile_data, assignments) = palette_tile_data(&optimised_results, &images);
|
||||||
|
|
||||||
let palette_data = palette_data.iter().map(|colours| {
|
let palette_data = palette_data.iter().map(|colours| {
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -214,38 +279,23 @@ fn convert_image(
|
||||||
parent: &Path,
|
parent: &Path,
|
||||||
variable_name: &str,
|
variable_name: &str,
|
||||||
crate_prefix: &str,
|
crate_prefix: &str,
|
||||||
|
optimisation_results: &Palette16OptimisationResults,
|
||||||
|
assignment_offset: Option<usize>,
|
||||||
) -> proc_macro2::TokenStream {
|
) -> proc_macro2::TokenStream {
|
||||||
let image_filename = &parent.join(&settings.filename());
|
let image_filename = &parent.join(&settings.filename());
|
||||||
let image = Image::load_from_file(image_filename);
|
let image = Image::load_from_file(image_filename);
|
||||||
|
|
||||||
let tile_size = settings.tilesize().to_size();
|
|
||||||
if image.width % tile_size != 0 || image.height % tile_size != 0 {
|
|
||||||
panic!("Image size not a multiple of tile size");
|
|
||||||
}
|
|
||||||
|
|
||||||
let optimiser = optimiser_for_image(&image, tile_size, settings.transparent_colour());
|
|
||||||
let optimisation_results = optimiser.optimise_palettes();
|
|
||||||
|
|
||||||
rust_generator::generate_code(
|
rust_generator::generate_code(
|
||||||
variable_name,
|
variable_name,
|
||||||
&optimisation_results,
|
optimisation_results,
|
||||||
&image,
|
&image,
|
||||||
&image_filename.to_string_lossy(),
|
&image_filename.to_string_lossy(),
|
||||||
settings.tilesize(),
|
settings.tilesize(),
|
||||||
crate_prefix.to_owned(),
|
crate_prefix.to_owned(),
|
||||||
|
assignment_offset,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optimiser_for_image(
|
|
||||||
image: &Image,
|
|
||||||
tile_size: usize,
|
|
||||||
transparent_colour: Option<Colour>,
|
|
||||||
) -> palette16::Palette16Optimiser {
|
|
||||||
let mut palette_optimiser = palette16::Palette16Optimiser::new(transparent_colour);
|
|
||||||
add_to_optimiser(&mut palette_optimiser, image, tile_size, transparent_colour);
|
|
||||||
palette_optimiser
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_to_optimiser(
|
fn add_to_optimiser(
|
||||||
palette_optimiser: &mut palette16::Palette16Optimiser,
|
palette_optimiser: &mut palette16::Palette16Optimiser,
|
||||||
image: &Image,
|
image: &Image,
|
||||||
|
@ -275,7 +325,7 @@ fn add_to_optimiser(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn palete_tile_data(
|
fn palette_tile_data(
|
||||||
optimiser: &Palette16OptimisationResults,
|
optimiser: &Palette16OptimisationResults,
|
||||||
images: &[Image],
|
images: &[Image],
|
||||||
) -> (Vec<Vec<u16>>, Vec<u8>, Vec<usize>) {
|
) -> (Vec<Vec<u16>>, Vec<u8>, Vec<usize>) {
|
||||||
|
@ -295,41 +345,87 @@ fn palete_tile_data(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut tile_data = Vec::new();
|
let mut tile_data = Vec::new();
|
||||||
|
let tile_size = TileSize::Tile8;
|
||||||
|
|
||||||
for image in images {
|
for image in images {
|
||||||
let tile_size = 8;
|
add_image_to_tile_data(&mut tile_data, image, tile_size, optimiser, 0)
|
||||||
let tiles_x = image.width / tile_size;
|
}
|
||||||
let tiles_y = image.height / tile_size;
|
|
||||||
|
|
||||||
for y in 0..tiles_y {
|
let tile_data = collapse_to_4bpp(&tile_data);
|
||||||
for x in 0..tiles_x {
|
|
||||||
let palette_index = optimiser.assignments[y * tiles_x + x];
|
|
||||||
let palette = &optimiser.optimised_palettes[palette_index];
|
|
||||||
|
|
||||||
for inner_y in 0..tile_size / 8 {
|
let assignments = optimiser.assignments.clone();
|
||||||
for inner_x in 0..tile_size / 8 {
|
|
||||||
for j in inner_y * 8..inner_y * 8 + 8 {
|
(palette_data, tile_data, assignments)
|
||||||
for i in inner_x * 8..inner_x * 8 + 8 {
|
}
|
||||||
let colour = image.colour(x * tile_size + i, y * tile_size + j);
|
|
||||||
tile_data.push(
|
fn collapse_to_4bpp(tile_data: &[u8]) -> Vec<u8> {
|
||||||
palette.colour_index(colour, optimiser.transparent_colour),
|
tile_data
|
||||||
);
|
.chunks(2)
|
||||||
}
|
.map(|chunk| chunk[0] | (chunk[1] << 4))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_image_to_tile_data(
|
||||||
|
tile_data: &mut Vec<u8>,
|
||||||
|
image: &Image,
|
||||||
|
tile_size: TileSize,
|
||||||
|
optimiser: &Palette16OptimisationResults,
|
||||||
|
assignment_offset: usize,
|
||||||
|
) {
|
||||||
|
let tile_size = tile_size.to_size();
|
||||||
|
let tiles_x = image.width / tile_size;
|
||||||
|
let tiles_y = image.height / tile_size;
|
||||||
|
|
||||||
|
for y in 0..tiles_y {
|
||||||
|
for x in 0..tiles_x {
|
||||||
|
let palette_index = optimiser.assignments[y * tiles_x + x + assignment_offset];
|
||||||
|
let palette = &optimiser.optimised_palettes[palette_index];
|
||||||
|
|
||||||
|
for inner_y in 0..tile_size / 8 {
|
||||||
|
for inner_x in 0..tile_size / 8 {
|
||||||
|
for j in inner_y * 8..inner_y * 8 + 8 {
|
||||||
|
for i in inner_x * 8..inner_x * 8 + 8 {
|
||||||
|
let colour = image.colour(x * tile_size + i, y * tile_size + j);
|
||||||
|
tile_data
|
||||||
|
.push(palette.colour_index(colour, optimiser.transparent_colour));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let tile_data = tile_data
|
fn add_image_256_to_tile_data(
|
||||||
.chunks(2)
|
tile_data: &mut Vec<u8>,
|
||||||
.map(|chunk| chunk[0] | (chunk[1] << 4))
|
image: &Image,
|
||||||
|
tile_size: TileSize,
|
||||||
|
optimiser: &Palette16OptimisationResults,
|
||||||
|
) {
|
||||||
|
let tile_size = tile_size.to_size();
|
||||||
|
let tiles_x = image.width / tile_size;
|
||||||
|
let tiles_y = image.height / tile_size;
|
||||||
|
|
||||||
|
let all_colours: Vec<_> = optimiser
|
||||||
|
.optimised_palettes
|
||||||
|
.iter()
|
||||||
|
.flat_map(|p| p.colours())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let assignments = optimiser.assignments.clone();
|
for y in 0..tiles_y {
|
||||||
|
for x in 0..tiles_x {
|
||||||
(palette_data, tile_data, assignments)
|
for inner_y in 0..tile_size / 8 {
|
||||||
|
for inner_x in 0..tile_size / 8 {
|
||||||
|
for j in inner_y * 8..inner_y * 8 + 8 {
|
||||||
|
for i in inner_x * 8..inner_x * 8 + 8 {
|
||||||
|
let colour = image.colour(x * tile_size + i, y * tile_size + j);
|
||||||
|
tile_data.push(all_colours.iter().position(|c| **c == colour).unwrap() as u8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_group(expr: &Expr) -> &Expr {
|
fn flatten_group(expr: &Expr) -> &Expr {
|
||||||
|
|
|
@ -28,6 +28,19 @@ impl Palette16 {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn try_add_colour(&mut self, colour: Colour) -> bool {
|
||||||
|
if self.colours.contains(&colour) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.colours.len() == MAX_COLOURS_PER_PALETTE {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.colours.push(colour);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub fn colour_index(&self, colour: Colour, transparent_colour: Option<Colour>) -> u8 {
|
pub fn colour_index(&self, colour: Colour, transparent_colour: Option<Colour>) -> u8 {
|
||||||
let colour_to_search = match (transparent_colour, colour.is_transparent()) {
|
let colour_to_search = match (transparent_colour, colour.is_transparent()) {
|
||||||
(Some(transparent_colour), true) => transparent_colour,
|
(Some(transparent_colour), true) => transparent_colour,
|
||||||
|
@ -45,6 +58,10 @@ impl Palette16 {
|
||||||
}) as u8
|
}) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn colours(&self) -> impl Iterator<Item = &Colour> {
|
||||||
|
self.colours.iter()
|
||||||
|
}
|
||||||
|
|
||||||
fn union_length(&self, other: &Palette16) -> usize {
|
fn union_length(&self, other: &Palette16) -> usize {
|
||||||
self.colours
|
self.colours
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -150,9 +167,10 @@ impl Palette16Optimiser {
|
||||||
fn find_maximal_palette_for(&self, unsatisfied_palettes: &HashSet<Palette16>) -> Palette16 {
|
fn find_maximal_palette_for(&self, unsatisfied_palettes: &HashSet<Palette16>) -> Palette16 {
|
||||||
let mut palette = Palette16::new();
|
let mut palette = Palette16::new();
|
||||||
|
|
||||||
if let Some(transparent_colour) = self.transparent_colour {
|
palette.add_colour(
|
||||||
palette.add_colour(transparent_colour);
|
self.transparent_colour
|
||||||
}
|
.unwrap_or_else(|| Colour::from_rgb(255, 0, 255, 0)),
|
||||||
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut colour_usage = vec![0; MAX_COLOURS];
|
let mut colour_usage = vec![0; MAX_COLOURS];
|
||||||
|
|
72
agb-image-converter/src/palette256.rs
Normal file
72
agb-image-converter/src/palette256.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use std::{collections::HashSet, iter::FromIterator};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
colour::Colour,
|
||||||
|
image_loader::Image,
|
||||||
|
palette16::{Palette16, Palette16OptimisationResults},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Palette256 {
|
||||||
|
colours: HashSet<Colour>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Palette256 {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
colours: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_image(&mut self, image: &Image) {
|
||||||
|
for y in 0..image.height {
|
||||||
|
for x in 0..image.width {
|
||||||
|
self.colours.insert(image.colour(x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
self.colours.len() <= 256,
|
||||||
|
"Must have at most 256 colours in the palette"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn extend_results(
|
||||||
|
&self,
|
||||||
|
palette16: &Palette16OptimisationResults,
|
||||||
|
) -> Palette16OptimisationResults {
|
||||||
|
let optimised_palette_colours: Vec<_> = palette16
|
||||||
|
.optimised_palettes
|
||||||
|
.iter()
|
||||||
|
.flat_map(|p| p.colours())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let current_colours_set = HashSet::from_iter(optimised_palette_colours.iter().cloned());
|
||||||
|
let new_colours: HashSet<_> = self
|
||||||
|
.colours
|
||||||
|
.symmetric_difference(¤t_colours_set)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
new_colours.len() + optimised_palette_colours.len() <= 256,
|
||||||
|
"Cannot optimise 16 colour and 256 colour palettes together, produces too many colours"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut new_palettes = palette16.optimised_palettes.clone();
|
||||||
|
new_palettes.resize_with(16, Palette16::new);
|
||||||
|
|
||||||
|
for colour in new_colours {
|
||||||
|
for palette in new_palettes.iter_mut() {
|
||||||
|
if palette.try_add_colour(*colour) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Palette16OptimisationResults {
|
||||||
|
optimised_palettes: new_palettes,
|
||||||
|
assignments: palette16.assignments.clone(),
|
||||||
|
transparent_colour: palette16.transparent_colour,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::palette16::Palette16OptimisationResults;
|
use crate::palette16::Palette16OptimisationResults;
|
||||||
use crate::TileSize;
|
use crate::{add_image_256_to_tile_data, add_image_to_tile_data, collapse_to_4bpp, TileSize};
|
||||||
use crate::{image_loader::Image, ByteString};
|
use crate::{image_loader::Image, ByteString};
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
@ -7,18 +7,13 @@ use quote::{format_ident, quote};
|
||||||
|
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
pub(crate) fn generate_code(
|
pub(crate) fn generate_palette_code(
|
||||||
output_variable_name: &str,
|
|
||||||
results: &Palette16OptimisationResults,
|
results: &Palette16OptimisationResults,
|
||||||
image: &Image,
|
crate_prefix: &str,
|
||||||
image_filename: &str,
|
|
||||||
tile_size: TileSize,
|
|
||||||
crate_prefix: String,
|
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
let crate_prefix = format_ident!("{}", crate_prefix);
|
let crate_prefix = format_ident!("{}", crate_prefix);
|
||||||
let output_variable_name = format_ident!("{}", output_variable_name);
|
|
||||||
|
|
||||||
let palette_data = results.optimised_palettes.iter().map(|palette| {
|
let palettes = results.optimised_palettes.iter().map(|palette| {
|
||||||
let colours = palette
|
let colours = palette
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -34,57 +29,63 @@ pub(crate) fn generate_code(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let tile_size = tile_size.to_size();
|
quote! {
|
||||||
|
pub const PALETTES: &[#crate_prefix::display::palette16::Palette16] = &[#(#palettes),*];
|
||||||
let tiles_x = image.width / tile_size;
|
|
||||||
let tiles_y = image.height / tile_size;
|
|
||||||
|
|
||||||
let mut tile_data = vec![];
|
|
||||||
|
|
||||||
for y in 0..tiles_y {
|
|
||||||
for x in 0..tiles_x {
|
|
||||||
let palette_index = results.assignments[y * tiles_x + x];
|
|
||||||
let palette = &results.optimised_palettes[palette_index];
|
|
||||||
|
|
||||||
for inner_y in 0..tile_size / 8 {
|
|
||||||
for inner_x in 0..tile_size / 8 {
|
|
||||||
for j in inner_y * 8..inner_y * 8 + 8 {
|
|
||||||
for i in inner_x * 8..inner_x * 8 + 8 {
|
|
||||||
let colour = image.colour(x * tile_size + i, y * tile_size + j);
|
|
||||||
tile_data
|
|
||||||
.push(palette.colour_index(colour, results.transparent_colour));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let tile_data: Vec<_> = tile_data
|
pub(crate) fn generate_code(
|
||||||
.chunks(2)
|
output_variable_name: &str,
|
||||||
.map(|chunk| (chunk[1] << 4) | chunk[0])
|
results: &Palette16OptimisationResults,
|
||||||
.collect();
|
image: &Image,
|
||||||
|
image_filename: &str,
|
||||||
|
tile_size: TileSize,
|
||||||
|
crate_prefix: String,
|
||||||
|
assignment_offset: Option<usize>,
|
||||||
|
) -> TokenStream {
|
||||||
|
let crate_prefix = format_ident!("{}", crate_prefix);
|
||||||
|
let output_variable_name = format_ident!("{}", output_variable_name);
|
||||||
|
|
||||||
|
let (tile_data, assignments) = if let Some(assignment_offset) = assignment_offset {
|
||||||
|
let mut tile_data = Vec::new();
|
||||||
|
|
||||||
|
add_image_to_tile_data(&mut tile_data, image, tile_size, results, assignment_offset);
|
||||||
|
|
||||||
|
let tile_data = collapse_to_4bpp(&tile_data);
|
||||||
|
|
||||||
|
let num_tiles = image.width * image.height / tile_size.to_size().pow(2);
|
||||||
|
|
||||||
|
let assignments = results
|
||||||
|
.assignments
|
||||||
|
.iter()
|
||||||
|
.skip(assignment_offset)
|
||||||
|
.take(num_tiles)
|
||||||
|
.map(|&x| x as u8)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
(tile_data, assignments)
|
||||||
|
} else {
|
||||||
|
let mut tile_data = Vec::new();
|
||||||
|
|
||||||
|
add_image_256_to_tile_data(&mut tile_data, image, tile_size, results);
|
||||||
|
|
||||||
|
(tile_data, vec![])
|
||||||
|
};
|
||||||
|
|
||||||
let data = ByteString(&tile_data);
|
let data = ByteString(&tile_data);
|
||||||
|
|
||||||
let assignments = results.assignments.iter().map(|&x| x as u8);
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
pub const #output_variable_name: #crate_prefix::display::tile_data::TileData = {
|
pub const #output_variable_name: #crate_prefix::display::tile_data::TileData = {
|
||||||
const _: &[u8] = include_bytes!(#image_filename);
|
const _: &[u8] = include_bytes!(#image_filename);
|
||||||
|
|
||||||
const PALETTE_DATA: &[#crate_prefix::display::palette16::Palette16] = &[
|
|
||||||
#(#palette_data),*
|
|
||||||
];
|
|
||||||
|
|
||||||
const TILE_DATA: &[u8] = #data;
|
const TILE_DATA: &[u8] = #data;
|
||||||
|
|
||||||
const PALETTE_ASSIGNMENT: &[u8] = &[
|
const PALETTE_ASSIGNMENT: &[u8] = &[
|
||||||
#(#assignments),*
|
#(#assignments),*
|
||||||
];
|
];
|
||||||
|
|
||||||
#crate_prefix::display::tile_data::TileData::new(PALETTE_DATA, TILE_DATA, PALETTE_ASSIGNMENT)
|
#crate_prefix::display::tile_data::TileData::new(TILE_DATA, PALETTE_ASSIGNMENT)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,6 @@ debug = true
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[features]
|
|
||||||
freq18157 = []
|
|
||||||
freq32768 = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hound = "3.5"
|
hound = "3.5"
|
||||||
syn = "1"
|
syn = "1"
|
||||||
|
|
|
@ -6,15 +6,6 @@ use quote::{quote, ToTokens};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use syn::parse_macro_input;
|
use syn::parse_macro_input;
|
||||||
|
|
||||||
#[cfg(all(not(feature = "freq18157"), not(feature = "freq32768")))]
|
|
||||||
const FREQUENCY: u32 = 10512;
|
|
||||||
#[cfg(feature = "freq18157")]
|
|
||||||
const FREQUENCY: u32 = 18157;
|
|
||||||
#[cfg(feature = "freq32768")]
|
|
||||||
const FREQUENCY: u32 = 32768;
|
|
||||||
#[cfg(all(feature = "freq18157", feature = "freq32768"))]
|
|
||||||
compile_error!("Must have at most one of freq18157 or freq32768 features enabled");
|
|
||||||
|
|
||||||
use quote::TokenStreamExt;
|
use quote::TokenStreamExt;
|
||||||
struct ByteString<'a>(&'a [u8]);
|
struct ByteString<'a>(&'a [u8]);
|
||||||
impl ToTokens for ByteString<'_> {
|
impl ToTokens for ByteString<'_> {
|
||||||
|
@ -37,13 +28,6 @@ pub fn include_wav(input: TokenStream) -> TokenStream {
|
||||||
let wav_reader = hound::WavReader::open(&path)
|
let wav_reader = hound::WavReader::open(&path)
|
||||||
.unwrap_or_else(|_| panic!("Failed to load file {}", include_path));
|
.unwrap_or_else(|_| panic!("Failed to load file {}", include_path));
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
wav_reader.spec().sample_rate,
|
|
||||||
FREQUENCY,
|
|
||||||
"agb currently only supports sample rate of {}Hz",
|
|
||||||
FREQUENCY
|
|
||||||
);
|
|
||||||
|
|
||||||
let samples: Vec<u8> = samples_from_reader(wav_reader).collect();
|
let samples: Vec<u8> = samples_from_reader(wav_reader).collect();
|
||||||
let samples = ByteString(&samples);
|
let samples = ByteString(&samples);
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@ debug = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["testing"]
|
default = ["testing"]
|
||||||
freq18157 = ["agb_sound_converter/freq18157"]
|
|
||||||
freq32768 = ["agb_sound_converter/freq32768"]
|
|
||||||
testing = []
|
testing = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -34,7 +32,3 @@ rustc-hash = { version = "1", default-features = false }
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
default-target = "thumbv6m-none-eabi"
|
default-target = "thumbv6m-none-eabi"
|
||||||
targets = []
|
targets = []
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "mixer_32768"
|
|
||||||
required-features = ["freq32768"]
|
|
60
agb/examples/affine_background.rs
Normal file
60
agb/examples/affine_background.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use agb::{
|
||||||
|
display::{
|
||||||
|
tiled::{AffineBackgroundSize, TileFormat, TileSet, TiledMap},
|
||||||
|
Priority,
|
||||||
|
},
|
||||||
|
fixnum::{num, Num},
|
||||||
|
include_gfx,
|
||||||
|
};
|
||||||
|
|
||||||
|
include_gfx!("examples/affine_tiles.toml");
|
||||||
|
|
||||||
|
#[agb::entry]
|
||||||
|
fn main(mut gba: agb::Gba) -> ! {
|
||||||
|
let (gfx, mut vram) = gba.display.video.tiled2();
|
||||||
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
|
|
||||||
|
let tileset = TileSet::new(affine_tiles::water_tiles.tiles, TileFormat::EightBpp);
|
||||||
|
|
||||||
|
vram.set_background_palettes(affine_tiles::PALETTES);
|
||||||
|
|
||||||
|
let mut bg = gfx.background(Priority::P0, AffineBackgroundSize::Background32x32);
|
||||||
|
|
||||||
|
for y in 0..32u16 {
|
||||||
|
for x in 0..32u16 {
|
||||||
|
bg.set_tile(&mut vram, (x, y).into(), &tileset, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bg.commit(&mut vram);
|
||||||
|
bg.show();
|
||||||
|
|
||||||
|
let mut rotation: Num<u16, 8> = num!(0.);
|
||||||
|
let rotation_increase = num!(1.);
|
||||||
|
|
||||||
|
let mut input = agb::input::ButtonController::new();
|
||||||
|
|
||||||
|
let mut scroll_x = 0;
|
||||||
|
let mut scroll_y = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
input.update();
|
||||||
|
scroll_x += input.x_tri() as i32;
|
||||||
|
scroll_y += input.y_tri() as i32;
|
||||||
|
|
||||||
|
let scroll_pos = (scroll_x as i16, scroll_y as i16);
|
||||||
|
bg.set_scroll_pos(scroll_pos.into());
|
||||||
|
bg.set_transform((0, 0), (1, 1), rotation);
|
||||||
|
|
||||||
|
rotation += rotation_increase;
|
||||||
|
if rotation >= num!(255.) {
|
||||||
|
rotation = 0.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
vblank.wait_for_vblank();
|
||||||
|
bg.commit(&mut vram);
|
||||||
|
}
|
||||||
|
}
|
6
agb/examples/affine_tiles.toml
Normal file
6
agb/examples/affine_tiles.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
version = "1.0"
|
||||||
|
|
||||||
|
[image.water_tiles]
|
||||||
|
filename = "water_tiles.png"
|
||||||
|
tile_size = "8x8"
|
||||||
|
colours = 256
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use agb::{
|
use agb::{
|
||||||
display::{
|
display::{
|
||||||
tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting},
|
tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, TiledMap},
|
||||||
Priority,
|
Priority,
|
||||||
},
|
},
|
||||||
include_gfx,
|
include_gfx,
|
||||||
|
@ -18,7 +18,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
|
|
||||||
let tileset = TileSet::new(water_tiles::water_tiles.tiles, TileFormat::FourBpp);
|
let tileset = TileSet::new(water_tiles::water_tiles.tiles, TileFormat::FourBpp);
|
||||||
|
|
||||||
vram.set_background_palettes(water_tiles::water_tiles.palettes);
|
vram.set_background_palettes(water_tiles::PALETTES);
|
||||||
|
|
||||||
let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use agb::{
|
use agb::{
|
||||||
display::tiled::{TileFormat, TileSet, TileSetting},
|
display::tiled::{TileFormat, TileSet, TileSetting, TiledMap},
|
||||||
display::{
|
display::{
|
||||||
object::{Object, ObjectController, Size, Sprite},
|
object::{Object, ObjectController, Size, Sprite},
|
||||||
palette16::Palette16,
|
palette16::Palette16,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use agb::display::{
|
use agb::display::{
|
||||||
palette16::Palette16,
|
palette16::Palette16,
|
||||||
tiled::{RegularBackgroundSize, TileSetting},
|
tiled::{RegularBackgroundSize, TileSetting, TiledMap},
|
||||||
Priority,
|
Priority,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
|
|
||||||
use agb::{
|
use agb::{
|
||||||
display::{
|
display::{
|
||||||
tiled::{RegularBackgroundSize, RegularMap, TileSetting, VRamManager},
|
tiled::{RegularBackgroundSize, RegularMap, TileSetting, TiledMap, VRamManager},
|
||||||
Font, Priority,
|
Font, Priority,
|
||||||
},
|
},
|
||||||
include_font, include_wav,
|
include_font, include_wav,
|
||||||
sound::mixer::SoundChannel,
|
sound::mixer::{Frequency, SoundChannel},
|
||||||
Gba,
|
Gba,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut timer = timer_controller.timer2;
|
let mut timer = timer_controller.timer2;
|
||||||
timer.set_enabled(true);
|
timer.set_enabled(true);
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
let _interrupt = mixer.setup_interrupt_handler();
|
let _interrupt = mixer.setup_interrupt_handler();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use agb::fixnum::Num;
|
use agb::fixnum::Num;
|
||||||
use agb::input::{Button, ButtonController, Tri};
|
use agb::input::{Button, ButtonController, Tri};
|
||||||
use agb::sound::mixer::SoundChannel;
|
use agb::sound::mixer::{Frequency, SoundChannel};
|
||||||
use agb::{fixnum::num, include_wav, Gba};
|
use agb::{fixnum::num, include_wav, Gba};
|
||||||
|
|
||||||
// Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com
|
// Music - "Dead Code" by Josh Woodward, free download at http://joshwoodward.com
|
||||||
|
@ -14,7 +14,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut input = ButtonController::new();
|
let mut input = ButtonController::new();
|
||||||
let vblank_provider = agb::interrupt::VBlank::get();
|
let vblank_provider = agb::interrupt::VBlank::get();
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
|
||||||
let channel = SoundChannel::new(DEAD_CODE);
|
let channel = SoundChannel::new(DEAD_CODE);
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
|
|
||||||
use agb::{
|
use agb::{
|
||||||
display::{
|
display::{
|
||||||
tiled::{RegularBackgroundSize, RegularMap, TileSetting, VRamManager},
|
tiled::{RegularBackgroundSize, RegularMap, TileSetting, TiledMap, VRamManager},
|
||||||
Font, Priority,
|
Font, Priority,
|
||||||
},
|
},
|
||||||
include_font, include_wav,
|
include_font, include_wav,
|
||||||
sound::mixer::SoundChannel,
|
sound::mixer::{Frequency, SoundChannel},
|
||||||
Gba,
|
Gba,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ fn main(mut gba: Gba) -> ! {
|
||||||
let mut timer = timer_controller.timer2;
|
let mut timer = timer_controller.timer2;
|
||||||
timer.set_enabled(true);
|
timer.set_enabled(true);
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
|
||||||
let mut channel = SoundChannel::new(LET_IT_IN);
|
let mut channel = SoundChannel::new(LET_IT_IN);
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use agb::{
|
use agb::{
|
||||||
display::{
|
display::{
|
||||||
tiled::{RegularBackgroundSize, TileSetting},
|
tiled::{RegularBackgroundSize, TileSetting, TiledMap},
|
||||||
Font, Priority,
|
Font, Priority,
|
||||||
},
|
},
|
||||||
include_font,
|
include_font,
|
||||||
|
|
|
@ -5,5 +5,4 @@ crate_prefix = "crate"
|
||||||
|
|
||||||
[image.test_logo]
|
[image.test_logo]
|
||||||
filename = "test_logo.png"
|
filename = "test_logo.png"
|
||||||
transparent_colour = "010101"
|
|
||||||
tile_size = "8x8"
|
tile_size = "8x8"
|
||||||
|
|
|
@ -43,7 +43,7 @@ impl BumpAllocator {
|
||||||
let resulting_ptr = ptr + amount_to_add;
|
let resulting_ptr = ptr + amount_to_add;
|
||||||
let new_current_ptr = resulting_ptr + layout.size();
|
let new_current_ptr = resulting_ptr + layout.size();
|
||||||
|
|
||||||
if new_current_ptr as usize >= (self.start_end.borrow(cs).end)() {
|
if new_current_ptr >= (self.start_end.borrow(cs).end)() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager};
|
use super::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager};
|
||||||
|
|
||||||
crate::include_gfx!("gfx/agb_logo.toml");
|
crate::include_gfx!("gfx/agb_logo.toml");
|
||||||
|
|
||||||
pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
|
pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
|
||||||
vram.set_background_palettes(agb_logo::test_logo.palettes);
|
vram.set_background_palettes(agb_logo::PALETTES);
|
||||||
|
|
||||||
let background_tilemap = TileSet::new(agb_logo::test_logo.tiles, TileFormat::FourBpp);
|
let background_tilemap = TileSet::new(agb_logo::test_logo.tiles, TileFormat::FourBpp);
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) {
|
||||||
map.commit(vram);
|
map.commit(vram);
|
||||||
map.show();
|
map.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::display::{tiled::RegularBackgroundSize, Priority};
|
use crate::display::{tiled::RegularBackgroundSize, Priority};
|
||||||
|
|
|
@ -210,6 +210,7 @@ impl<'a> Drop for TextRenderer<'a> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::display::tiled::TiledMap;
|
||||||
const FONT: Font = crate::include_font!("examples/font/yoster.ttf", 12);
|
const FONT: Font = crate::include_font!("examples/font/yoster.ttf", 12);
|
||||||
|
|
||||||
#[test_case]
|
#[test_case]
|
||||||
|
|
|
@ -19,7 +19,7 @@ use super::{Priority, DISPLAY_CONTROL};
|
||||||
use crate::agb_alloc::block_allocator::BlockAllocator;
|
use crate::agb_alloc::block_allocator::BlockAllocator;
|
||||||
use crate::agb_alloc::bump_allocator::StartEnd;
|
use crate::agb_alloc::bump_allocator::StartEnd;
|
||||||
use crate::dma;
|
use crate::dma;
|
||||||
use crate::fixnum::Vector2D;
|
use crate::fixnum::{Num, Vector2D};
|
||||||
use crate::hash_map::HashMap;
|
use crate::hash_map::HashMap;
|
||||||
|
|
||||||
use attributes::*;
|
use attributes::*;
|
||||||
|
@ -724,7 +724,7 @@ impl ObjectController {
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
(OBJECT_ATTRIBUTE_MEMORY as *mut u16)
|
(OBJECT_ATTRIBUTE_MEMORY as *mut u16)
|
||||||
.add((i as usize) * 4)
|
.add(i * 4)
|
||||||
.write_volatile(HIDDEN_VALUE);
|
.write_volatile(HIDDEN_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -871,7 +871,7 @@ impl ObjectController {
|
||||||
let shape_size = sprite.sprite.0.size.shape_size();
|
let shape_size = sprite.sprite.0.size.shape_size();
|
||||||
attrs
|
attrs
|
||||||
.a2
|
.a2
|
||||||
.set_palete_bank((sprite.sprite.0.palette.0.location.0) as u8);
|
.set_palette_bank((sprite.sprite.0.palette.0.location.0) as u8);
|
||||||
attrs.a0.set_shape(shape_size.0);
|
attrs.a0.set_shape(shape_size.0);
|
||||||
attrs.a1a.set_size(shape_size.1);
|
attrs.a1a.set_size(shape_size.1);
|
||||||
attrs.a1s.set_size(shape_size.1);
|
attrs.a1s.set_size(shape_size.1);
|
||||||
|
@ -887,7 +887,7 @@ impl ObjectController {
|
||||||
});
|
});
|
||||||
|
|
||||||
let loan = Loan {
|
let loan = Loan {
|
||||||
index: index as u8,
|
index,
|
||||||
controller: self.inner,
|
controller: self.inner,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -980,7 +980,7 @@ impl<'a> Object<'a> {
|
||||||
object_inner
|
object_inner
|
||||||
.attrs
|
.attrs
|
||||||
.a2
|
.a2
|
||||||
.set_palete_bank(sprite.sprite.0.palette.0.location.0 as u8);
|
.set_palette_bank(sprite.sprite.0.palette.0.location.0 as u8);
|
||||||
object_inner.attrs.a0.set_shape(shape_size.0);
|
object_inner.attrs.a0.set_shape(shape_size.0);
|
||||||
object_inner.attrs.a1a.set_size(shape_size.1);
|
object_inner.attrs.a1a.set_size(shape_size.1);
|
||||||
object_inner.attrs.a1s.set_size(shape_size.1);
|
object_inner.attrs.a1s.set_size(shape_size.1);
|
||||||
|
@ -1027,8 +1027,8 @@ impl<'a> Object<'a> {
|
||||||
pub fn set_x(&mut self, x: u16) -> &mut Self {
|
pub fn set_x(&mut self, x: u16) -> &mut Self {
|
||||||
{
|
{
|
||||||
let mut object_inner = unsafe { self.object_inner() };
|
let mut object_inner = unsafe { self.object_inner() };
|
||||||
object_inner.attrs.a1a.set_x(x.rem_euclid(1 << 9) as u16);
|
object_inner.attrs.a1a.set_x(x.rem_euclid(1 << 9));
|
||||||
object_inner.attrs.a1s.set_x(x.rem_euclid(1 << 9) as u16);
|
object_inner.attrs.a1s.set_x(x.rem_euclid(1 << 9));
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -1236,6 +1236,42 @@ enum ColourMode {
|
||||||
Eight,
|
Eight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The parameters used for the PPU's affine transformation function
|
||||||
|
/// that can apply to objects and background layers in modes 1 and 2.
|
||||||
|
/// This can be obtained from X/Y scale and rotation angle with
|
||||||
|
/// [`agb::syscall::affine_matrix`].
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(C, packed(4))]
|
||||||
|
pub struct AffineMatrixAttributes {
|
||||||
|
/// Adjustment made to *X* coordinate when drawing *horizontal* lines.
|
||||||
|
/// Also known as "dx".
|
||||||
|
/// Typically computed as `x_scale * cos(angle)`.
|
||||||
|
pub p_a: Num<i16, 8>,
|
||||||
|
/// Adjustment made to *X* coordinate along *vertical* lines.
|
||||||
|
/// Also known as "dmx".
|
||||||
|
/// Typically computed as `y_scale * sin(angle)`.
|
||||||
|
pub p_b: Num<i16, 8>,
|
||||||
|
/// Adjustment made to *Y* coordinate along *horizontal* lines.
|
||||||
|
/// Also known as "dy".
|
||||||
|
/// Typically computed as `-x_scale * sin(angle)`.
|
||||||
|
pub p_c: Num<i16, 8>,
|
||||||
|
/// Adjustment made to *Y* coordinate along *vertical* lines.
|
||||||
|
/// Also known as "dmy".
|
||||||
|
/// Typically computed as `y_scale * cos(angle)`.
|
||||||
|
pub p_d: Num<i16, 8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AffineMatrixAttributes {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
p_a: 1.into(),
|
||||||
|
p_b: Default::default(),
|
||||||
|
p_c: Default::default(),
|
||||||
|
p_d: 1.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this mod is not public, so the internal parts don't need documenting.
|
// this mod is not public, so the internal parts don't need documenting.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod attributes {
|
mod attributes {
|
||||||
|
@ -1275,7 +1311,7 @@ mod attributes {
|
||||||
pub(super) struct ObjectAttribute2 {
|
pub(super) struct ObjectAttribute2 {
|
||||||
pub tile_index: B10,
|
pub tile_index: B10,
|
||||||
pub priority: Priority,
|
pub priority: Priority,
|
||||||
pub palete_bank: B4,
|
pub palette_bank: B4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,13 @@
|
||||||
use crate::display::palette16::Palette16;
|
#[non_exhaustive]
|
||||||
|
|
||||||
pub struct TileData {
|
pub struct TileData {
|
||||||
pub palettes: &'static [Palette16],
|
|
||||||
pub tiles: &'static [u8],
|
pub tiles: &'static [u8],
|
||||||
pub palette_assignments: &'static [u8],
|
pub palette_assignments: &'static [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TileData {
|
impl TileData {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new(
|
pub const fn new(tiles: &'static [u8], palette_assignments: &'static [u8]) -> Self {
|
||||||
palettes: &'static [Palette16],
|
|
||||||
tiles: &'static [u8],
|
|
||||||
palette_assignments: &'static [u8],
|
|
||||||
) -> Self {
|
|
||||||
TileData {
|
TileData {
|
||||||
palettes,
|
|
||||||
tiles,
|
tiles,
|
||||||
palette_assignments,
|
palette_assignments,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
use super::{BackgroundID, MapLoan, RegularMap, TileSet, TileSetting, VRamManager};
|
use super::{
|
||||||
|
BackgroundID, BackgroundSizePrivate, MapLoan, RegularMap, TileSet, TileSetting, TiledMap,
|
||||||
|
VRamManager,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display,
|
display,
|
||||||
|
@ -157,7 +160,7 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
/// # );
|
/// # );
|
||||||
/// #
|
/// #
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// let start_position = agb::fixnum::Vector2D::new(10, 10);
|
/// let start_position = agb::fixnum::Vector2D::new(10, 10);
|
||||||
/// backdrop.init(&mut vram, start_position, &mut || {
|
/// backdrop.init(&mut vram, start_position, &mut || {
|
||||||
/// vblank.wait_for_vblank();
|
/// vblank.wait_for_vblank();
|
||||||
|
@ -232,7 +235,7 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
/// # );
|
/// # );
|
||||||
/// #
|
/// #
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// let start_position = agb::fixnum::Vector2D::new(10, 10);
|
/// let start_position = agb::fixnum::Vector2D::new(10, 10);
|
||||||
/// while backdrop.init_partial(&mut vram, start_position) == PartialUpdateStatus::Continue {
|
/// while backdrop.init_partial(&mut vram, start_position) == PartialUpdateStatus::Continue {
|
||||||
/// vblank.wait_for_vblank();
|
/// vblank.wait_for_vblank();
|
||||||
|
@ -254,13 +257,9 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
let y_end = div_ceil(self.current_pos.y + display::HEIGHT, 8) + 1;
|
let y_end = div_ceil(self.current_pos.y + display::HEIGHT, 8) + 1;
|
||||||
|
|
||||||
let offset = self.current_pos - (x_start * 8, y_start * 8).into();
|
let offset = self.current_pos - (x_start * 8, y_start * 8).into();
|
||||||
let offset_scroll = (
|
|
||||||
self.map.size().tile_pos_x(offset.x),
|
|
||||||
self.map.size().tile_pos_y(offset.y),
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
self.map.set_scroll_pos(offset_scroll);
|
self.map
|
||||||
|
.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;
|
||||||
|
@ -383,11 +382,7 @@ impl<'a> InfiniteScrolledMap<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_scroll = self.map.scroll_pos();
|
let current_scroll = self.map.scroll_pos();
|
||||||
let new_scroll = (
|
let new_scroll = current_scroll + (difference.x as i16, difference.y as i16).into();
|
||||||
size.px_offset_x(i32::from(current_scroll.x) + difference.x),
|
|
||||||
size.px_offset_y(i32::from(current_scroll.y) + difference.y),
|
|
||||||
)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
self.map.set_scroll_pos(new_scroll);
|
self.map.set_scroll_pos(new_scroll);
|
||||||
|
|
||||||
|
|
|
@ -2,31 +2,182 @@ use core::cell::RefCell;
|
||||||
use core::ops::{Deref, DerefMut};
|
use core::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::bitarray::Bitarray;
|
use crate::bitarray::Bitarray;
|
||||||
use crate::display::{Priority, DISPLAY_CONTROL};
|
use crate::display::{object::AffineMatrixAttributes, Priority, DISPLAY_CONTROL};
|
||||||
use crate::dma::dma_copy16;
|
use crate::dma::dma_copy16;
|
||||||
use crate::fixnum::Vector2D;
|
use crate::fixnum::{Num, Vector2D};
|
||||||
use crate::memory_mapped::MemoryMapped;
|
use crate::memory_mapped::MemoryMapped;
|
||||||
|
|
||||||
use super::{BackgroundID, RegularBackgroundSize, Tile, TileSet, TileSetting, VRamManager};
|
use super::{
|
||||||
|
AffineBackgroundSize, BackgroundID, BackgroundSize, BackgroundSizePrivate,
|
||||||
|
RegularBackgroundSize, Tile, TileFormat, TileIndex, TileSet, TileSetting, VRamManager,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::syscall::BgAffineSetData;
|
||||||
use alloc::{vec, vec::Vec};
|
use alloc::{vec, vec::Vec};
|
||||||
|
|
||||||
|
pub trait TiledMapTypes: private::Sealed {
|
||||||
|
type Size: BackgroundSize + Copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TiledMapPrivate: TiledMapTypes {
|
||||||
|
type TileType: Into<TileIndex> + Copy + Default + Eq + PartialEq;
|
||||||
|
type AffineMatrix;
|
||||||
|
|
||||||
|
fn tiles_mut(&mut self) -> &mut [Self::TileType];
|
||||||
|
fn tiles_dirty(&mut self) -> &mut bool;
|
||||||
|
|
||||||
|
fn background_id(&self) -> usize;
|
||||||
|
fn screenblock(&self) -> usize;
|
||||||
|
fn priority(&self) -> Priority;
|
||||||
|
fn map_size(&self) -> Self::Size;
|
||||||
|
|
||||||
|
fn update_bg_registers(&self);
|
||||||
|
|
||||||
|
fn scroll_pos(&self) -> Vector2D<i16>;
|
||||||
|
fn set_scroll_pos(&mut self, new_pos: Vector2D<i16>);
|
||||||
|
|
||||||
|
fn bg_control_register(&self) -> MemoryMapped<u16> {
|
||||||
|
unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id()) }
|
||||||
|
}
|
||||||
|
fn screenblock_memory(&self) -> *mut u16 {
|
||||||
|
(0x0600_0000 + 0x1000 * self.screenblock() / 2) as *mut u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait which describes methods available on both tiled maps and affine maps. Note that
|
||||||
|
/// it is 'sealed' so you cannot implement this yourself.
|
||||||
|
pub trait TiledMap: TiledMapTypes {
|
||||||
|
fn clear(&mut self, vram: &mut VRamManager);
|
||||||
|
fn show(&mut self);
|
||||||
|
fn hide(&mut self);
|
||||||
|
fn commit(&mut self, vram: &mut VRamManager);
|
||||||
|
fn size(&self) -> Self::Size;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn scroll_pos(&self) -> Vector2D<i16>;
|
||||||
|
fn set_scroll_pos(&mut self, pos: Vector2D<i16>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> TiledMap for T
|
||||||
|
where
|
||||||
|
T: TiledMapPrivate,
|
||||||
|
T::Size: BackgroundSizePrivate,
|
||||||
|
{
|
||||||
|
fn clear(&mut self, vram: &mut VRamManager) {
|
||||||
|
for tile in self.tiles_mut() {
|
||||||
|
if *tile != Default::default() {
|
||||||
|
vram.remove_tile((*tile).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
*tile = Default::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self) {
|
||||||
|
let mode = DISPLAY_CONTROL.get();
|
||||||
|
let new_mode = mode | (1 << (self.background_id() + 0x08)) as u16;
|
||||||
|
DISPLAY_CONTROL.set(new_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide(&mut self) {
|
||||||
|
let mode = DISPLAY_CONTROL.get();
|
||||||
|
let new_mode = mode & !(1 << (self.background_id() + 0x08)) as u16;
|
||||||
|
DISPLAY_CONTROL.set(new_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit(&mut self, vram: &mut VRamManager) {
|
||||||
|
let new_bg_control_value = (self.priority() as u16)
|
||||||
|
| ((self.screenblock() as u16) << 8)
|
||||||
|
| (self.map_size().size_flag() << 14);
|
||||||
|
|
||||||
|
self.bg_control_register().set(new_bg_control_value);
|
||||||
|
self.update_bg_registers();
|
||||||
|
|
||||||
|
let screenblock_memory = self.screenblock_memory();
|
||||||
|
let x: TileIndex = unsafe { *self.tiles_mut().get_unchecked(0) }.into();
|
||||||
|
let x = x.format().tile_size() / TileFormat::FourBpp.tile_size();
|
||||||
|
if *self.tiles_dirty() {
|
||||||
|
unsafe {
|
||||||
|
dma_copy16(
|
||||||
|
self.tiles_mut().as_ptr() as *const u16,
|
||||||
|
screenblock_memory,
|
||||||
|
self.map_size().num_tiles() / x,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vram.gc();
|
||||||
|
|
||||||
|
*self.tiles_dirty() = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> T::Size {
|
||||||
|
self.map_size()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn scroll_pos(&self) -> Vector2D<i16> {
|
||||||
|
TiledMapPrivate::scroll_pos(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scroll_pos(&mut self, pos: Vector2D<i16>) {
|
||||||
|
TiledMapPrivate::set_scroll_pos(self, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RegularMap {
|
pub struct RegularMap {
|
||||||
background_id: u8,
|
background_id: u8,
|
||||||
|
|
||||||
screenblock: u8,
|
screenblock: u8,
|
||||||
x_scroll: u16,
|
|
||||||
y_scroll: u16,
|
|
||||||
priority: Priority,
|
priority: Priority,
|
||||||
|
size: RegularBackgroundSize,
|
||||||
|
|
||||||
|
scroll: Vector2D<i16>,
|
||||||
|
|
||||||
tiles: Vec<Tile>,
|
tiles: Vec<Tile>,
|
||||||
tiles_dirty: bool,
|
tiles_dirty: bool,
|
||||||
|
|
||||||
size: RegularBackgroundSize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const TRANSPARENT_TILE_INDEX: u16 = (1 << 10) - 1;
|
pub const TRANSPARENT_TILE_INDEX: u16 = (1 << 10) - 1;
|
||||||
|
|
||||||
|
impl TiledMapTypes for RegularMap {
|
||||||
|
type Size = RegularBackgroundSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiledMapPrivate for RegularMap {
|
||||||
|
type TileType = Tile;
|
||||||
|
type AffineMatrix = ();
|
||||||
|
|
||||||
|
fn tiles_mut(&mut self) -> &mut [Self::TileType] {
|
||||||
|
&mut self.tiles
|
||||||
|
}
|
||||||
|
fn tiles_dirty(&mut self) -> &mut bool {
|
||||||
|
&mut self.tiles_dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn background_id(&self) -> usize {
|
||||||
|
self.background_id as usize
|
||||||
|
}
|
||||||
|
fn screenblock(&self) -> usize {
|
||||||
|
self.screenblock as usize
|
||||||
|
}
|
||||||
|
fn priority(&self) -> Priority {
|
||||||
|
self.priority
|
||||||
|
}
|
||||||
|
fn map_size(&self) -> Self::Size {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
fn update_bg_registers(&self) {
|
||||||
|
self.x_register().set(self.scroll.x);
|
||||||
|
self.y_register().set(self.scroll.y);
|
||||||
|
}
|
||||||
|
fn scroll_pos(&self) -> Vector2D<i16> {
|
||||||
|
self.scroll
|
||||||
|
}
|
||||||
|
fn set_scroll_pos(&mut self, new_pos: Vector2D<i16>) {
|
||||||
|
self.scroll = new_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RegularMap {
|
impl RegularMap {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
background_id: u8,
|
background_id: u8,
|
||||||
|
@ -36,16 +187,14 @@ impl RegularMap {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
background_id,
|
background_id,
|
||||||
|
|
||||||
screenblock,
|
screenblock,
|
||||||
x_scroll: 0,
|
|
||||||
y_scroll: 0,
|
|
||||||
priority,
|
priority,
|
||||||
|
size,
|
||||||
|
|
||||||
|
scroll: Default::default(),
|
||||||
|
|
||||||
tiles: vec![Default::default(); size.num_tiles()],
|
tiles: vec![Default::default(); size.num_tiles()],
|
||||||
tiles_dirty: true,
|
tiles_dirty: true,
|
||||||
|
|
||||||
size,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,11 +205,11 @@ impl RegularMap {
|
||||||
tileset: &TileSet<'_>,
|
tileset: &TileSet<'_>,
|
||||||
tile_setting: TileSetting,
|
tile_setting: TileSetting,
|
||||||
) {
|
) {
|
||||||
let pos = self.size.gba_offset(pos);
|
let pos = self.map_size().gba_offset(pos);
|
||||||
|
|
||||||
let old_tile = self.tiles[pos];
|
let old_tile = self.tiles_mut()[pos];
|
||||||
if old_tile != Tile::default() {
|
if old_tile != Tile::default() {
|
||||||
vram.remove_tile(old_tile.tile_index());
|
vram.remove_tile(old_tile.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let tile_index = tile_setting.index();
|
let tile_index = tile_setting.index();
|
||||||
|
@ -77,86 +226,147 @@ impl RegularMap {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tiles[pos] = new_tile;
|
self.tiles_mut()[pos] = new_tile;
|
||||||
self.tiles_dirty = true;
|
*self.tiles_dirty() = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self, vram: &mut VRamManager) {
|
fn x_register(&self) -> MemoryMapped<i16> {
|
||||||
for tile in self.tiles.iter_mut() {
|
|
||||||
if *tile != Tile::default() {
|
|
||||||
vram.remove_tile(tile.tile_index());
|
|
||||||
}
|
|
||||||
|
|
||||||
*tile = Tile::default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show(&mut self) {
|
|
||||||
let mode = DISPLAY_CONTROL.get();
|
|
||||||
let new_mode = mode | (1 << (self.background_id + 0x08));
|
|
||||||
DISPLAY_CONTROL.set(new_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hide(&mut self) {
|
|
||||||
let mode = DISPLAY_CONTROL.get();
|
|
||||||
let new_mode = mode & !(1 << (self.background_id + 0x08));
|
|
||||||
DISPLAY_CONTROL.set(new_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn commit(&mut self, vram: &mut VRamManager) {
|
|
||||||
let new_bg_control_value = (self.priority as u16)
|
|
||||||
| (u16::from(self.screenblock) << 8)
|
|
||||||
| (self.size.size_flag() << 14);
|
|
||||||
|
|
||||||
self.bg_control_register().set(new_bg_control_value);
|
|
||||||
self.bg_h_offset().set(self.x_scroll);
|
|
||||||
self.bg_v_offset().set(self.y_scroll);
|
|
||||||
|
|
||||||
let screenblock_memory = self.screenblock_memory();
|
|
||||||
|
|
||||||
if self.tiles_dirty {
|
|
||||||
unsafe {
|
|
||||||
dma_copy16(
|
|
||||||
self.tiles.as_ptr() as *const u16,
|
|
||||||
screenblock_memory,
|
|
||||||
self.size.num_tiles(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vram.gc();
|
|
||||||
|
|
||||||
self.tiles_dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scroll_pos(&mut self, pos: Vector2D<u16>) {
|
|
||||||
self.x_scroll = pos.x;
|
|
||||||
self.y_scroll = pos.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn scroll_pos(&self) -> Vector2D<u16> {
|
|
||||||
(self.x_scroll, self.y_scroll).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn size(&self) -> RegularBackgroundSize {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn bg_control_register(&self) -> MemoryMapped<u16> {
|
|
||||||
unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id as usize) }
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn bg_h_offset(&self) -> MemoryMapped<u16> {
|
|
||||||
unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) }
|
unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn bg_v_offset(&self) -> MemoryMapped<u16> {
|
fn y_register(&self) -> MemoryMapped<i16> {
|
||||||
unsafe { MemoryMapped::new(0x0400_0012 + 4 * self.background_id as usize) }
|
unsafe { MemoryMapped::new(0x0400_0012 + 4 * self.background_id as usize) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fn screenblock_memory(&self) -> *mut u16 {
|
pub struct AffineMap {
|
||||||
(0x0600_0000 + 0x1000 * self.screenblock as usize / 2) as *mut u16
|
background_id: u8,
|
||||||
|
screenblock: u8,
|
||||||
|
priority: Priority,
|
||||||
|
size: AffineBackgroundSize,
|
||||||
|
|
||||||
|
scroll: Vector2D<i16>,
|
||||||
|
|
||||||
|
transform: BgAffineSetData,
|
||||||
|
|
||||||
|
tiles: Vec<u8>,
|
||||||
|
tiles_dirty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiledMapTypes for AffineMap {
|
||||||
|
type Size = AffineBackgroundSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiledMapPrivate for AffineMap {
|
||||||
|
type TileType = u8;
|
||||||
|
type AffineMatrix = AffineMatrixAttributes;
|
||||||
|
|
||||||
|
fn tiles_mut(&mut self) -> &mut [Self::TileType] {
|
||||||
|
&mut self.tiles
|
||||||
|
}
|
||||||
|
fn tiles_dirty(&mut self) -> &mut bool {
|
||||||
|
&mut self.tiles_dirty
|
||||||
|
}
|
||||||
|
fn background_id(&self) -> usize {
|
||||||
|
self.background_id as usize
|
||||||
|
}
|
||||||
|
fn screenblock(&self) -> usize {
|
||||||
|
self.screenblock as usize
|
||||||
|
}
|
||||||
|
fn priority(&self) -> Priority {
|
||||||
|
self.priority
|
||||||
|
}
|
||||||
|
fn map_size(&self) -> Self::Size {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
fn update_bg_registers(&self) {
|
||||||
|
let register_pos = self.transform.position;
|
||||||
|
self.bg_x().set(register_pos.x);
|
||||||
|
self.bg_y().set(register_pos.y);
|
||||||
|
self.bg_affine_matrix().set(self.transform.matrix);
|
||||||
|
}
|
||||||
|
fn scroll_pos(&self) -> Vector2D<i16> {
|
||||||
|
self.scroll
|
||||||
|
}
|
||||||
|
fn set_scroll_pos(&mut self, new_pos: Vector2D<i16>) {
|
||||||
|
self.scroll = new_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AffineMap {
|
||||||
|
pub(crate) fn new(
|
||||||
|
background_id: u8,
|
||||||
|
screenblock: u8,
|
||||||
|
priority: Priority,
|
||||||
|
size: AffineBackgroundSize,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
background_id,
|
||||||
|
screenblock,
|
||||||
|
priority,
|
||||||
|
size,
|
||||||
|
|
||||||
|
scroll: Default::default(),
|
||||||
|
|
||||||
|
transform: Default::default(),
|
||||||
|
|
||||||
|
tiles: vec![Default::default(); size.num_tiles()],
|
||||||
|
tiles_dirty: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tile(
|
||||||
|
&mut self,
|
||||||
|
vram: &mut VRamManager,
|
||||||
|
pos: Vector2D<u16>,
|
||||||
|
tileset: &TileSet<'_>,
|
||||||
|
tile_id: u8,
|
||||||
|
) {
|
||||||
|
let pos = self.map_size().gba_offset(pos);
|
||||||
|
|
||||||
|
let old_tile = self.tiles_mut()[pos];
|
||||||
|
if old_tile != 0 {
|
||||||
|
vram.remove_tile(old_tile.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let tile_index = tile_id as u16;
|
||||||
|
|
||||||
|
let new_tile = if tile_index != TRANSPARENT_TILE_INDEX {
|
||||||
|
let new_tile_idx = vram.add_tile(tileset, tile_index);
|
||||||
|
new_tile_idx.raw_index() as u8
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if old_tile == new_tile {
|
||||||
|
// no need to mark as dirty if nothing changes
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tiles_mut()[pos] = new_tile;
|
||||||
|
*self.tiles_dirty() = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_transform(
|
||||||
|
&mut self,
|
||||||
|
transform_origin: impl Into<Vector2D<Num<i32, 8>>>,
|
||||||
|
scale: impl Into<Vector2D<Num<i16, 8>>>,
|
||||||
|
rotation: impl Into<Num<u16, 8>>,
|
||||||
|
) {
|
||||||
|
let scale = scale.into();
|
||||||
|
let rotation = rotation.into();
|
||||||
|
self.transform =
|
||||||
|
crate::syscall::bg_affine_matrix(transform_origin.into(), self.scroll, scale, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bg_x(&self) -> MemoryMapped<Num<i32, 8>> {
|
||||||
|
unsafe { MemoryMapped::new(0x0400_0008 + 0x10 * self.background_id()) }
|
||||||
|
}
|
||||||
|
fn bg_y(&self) -> MemoryMapped<Num<i32, 8>> {
|
||||||
|
unsafe { MemoryMapped::new(0x0400_000c + 0x10 * self.background_id()) }
|
||||||
|
}
|
||||||
|
fn bg_affine_matrix(&self) -> MemoryMapped<AffineMatrixAttributes> {
|
||||||
|
unsafe { MemoryMapped::new(0x0400_0000 + 0x10 * self.background_id()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +375,7 @@ pub struct MapLoan<'a, T> {
|
||||||
background_id: u8,
|
background_id: u8,
|
||||||
screenblock_id: u8,
|
screenblock_id: u8,
|
||||||
screenblock_length: u8,
|
screenblock_length: u8,
|
||||||
regular_map_list: &'a RefCell<Bitarray<1>>,
|
map_list: &'a RefCell<Bitarray<1>>,
|
||||||
screenblock_list: &'a RefCell<Bitarray<1>>,
|
screenblock_list: &'a RefCell<Bitarray<1>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +399,7 @@ impl<'a, T> MapLoan<'a, T> {
|
||||||
background_id: u8,
|
background_id: u8,
|
||||||
screenblock_id: u8,
|
screenblock_id: u8,
|
||||||
screenblock_length: u8,
|
screenblock_length: u8,
|
||||||
regular_map_list: &'a RefCell<Bitarray<1>>,
|
map_list: &'a RefCell<Bitarray<1>>,
|
||||||
screenblock_list: &'a RefCell<Bitarray<1>>,
|
screenblock_list: &'a RefCell<Bitarray<1>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
MapLoan {
|
MapLoan {
|
||||||
|
@ -197,7 +407,7 @@ impl<'a, T> MapLoan<'a, T> {
|
||||||
background_id,
|
background_id,
|
||||||
screenblock_id,
|
screenblock_id,
|
||||||
screenblock_length,
|
screenblock_length,
|
||||||
regular_map_list,
|
map_list,
|
||||||
screenblock_list,
|
screenblock_list,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,7 +420,7 @@ impl<'a, T> MapLoan<'a, T> {
|
||||||
|
|
||||||
impl<'a, T> Drop for MapLoan<'a, T> {
|
impl<'a, T> Drop for MapLoan<'a, T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.regular_map_list
|
self.map_list
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.set(self.background_id as usize, false);
|
.set(self.background_id as usize, false);
|
||||||
|
|
||||||
|
@ -221,3 +431,10 @@ impl<'a, T> Drop for MapLoan<'a, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
pub trait Sealed {}
|
||||||
|
|
||||||
|
impl Sealed for super::RegularMap {}
|
||||||
|
impl Sealed for super::AffineMap {}
|
||||||
|
}
|
||||||
|
|
|
@ -1,28 +1,76 @@
|
||||||
mod infinite_scrolled_map;
|
mod infinite_scrolled_map;
|
||||||
mod map;
|
mod map;
|
||||||
mod tiled0;
|
mod tiled0;
|
||||||
|
mod tiled1;
|
||||||
|
mod tiled2;
|
||||||
mod vram_manager;
|
mod vram_manager;
|
||||||
|
|
||||||
|
use crate::bitarray::Bitarray;
|
||||||
|
use crate::display::Priority;
|
||||||
use agb_fixnum::Vector2D;
|
use agb_fixnum::Vector2D;
|
||||||
|
use core::cell::RefCell;
|
||||||
pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus};
|
pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus};
|
||||||
pub use map::{MapLoan, RegularMap};
|
pub use map::{AffineMap, MapLoan, RegularMap, TiledMap};
|
||||||
pub use tiled0::Tiled0;
|
pub use tiled0::Tiled0;
|
||||||
|
pub use tiled1::Tiled1;
|
||||||
|
pub use tiled2::Tiled2;
|
||||||
pub use vram_manager::{DynamicTile, TileFormat, TileIndex, TileSet, VRamManager};
|
pub use vram_manager::{DynamicTile, TileFormat, TileIndex, TileSet, VRamManager};
|
||||||
|
|
||||||
|
// affine layers start at BG2
|
||||||
|
pub(crate) const AFFINE_BG_ID_OFFSET: usize = 2;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(u16)]
|
||||||
pub enum RegularBackgroundSize {
|
pub enum RegularBackgroundSize {
|
||||||
Background32x32,
|
Background32x32 = 0,
|
||||||
Background64x32,
|
Background64x32 = 1,
|
||||||
Background32x64,
|
Background32x64 = 2,
|
||||||
Background64x64,
|
Background64x64 = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct BackgroundID(pub(crate) u8);
|
pub struct BackgroundID(pub(crate) u8);
|
||||||
|
|
||||||
impl RegularBackgroundSize {
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum AffineBackgroundSize {
|
||||||
|
Background16x16 = 0,
|
||||||
|
Background32x32 = 1,
|
||||||
|
Background64x64 = 2,
|
||||||
|
Background128x128 = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BackgroundSize {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn width(&self) -> u32 {
|
fn width(&self) -> u32;
|
||||||
|
#[must_use]
|
||||||
|
fn height(&self) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) trait BackgroundSizePrivate: BackgroundSize + Sized {
|
||||||
|
fn size_flag(self) -> u16;
|
||||||
|
fn num_tiles(&self) -> usize {
|
||||||
|
(self.width() * self.height()) as usize
|
||||||
|
}
|
||||||
|
fn num_screen_blocks(&self) -> usize;
|
||||||
|
fn gba_offset(&self, pos: Vector2D<u16>) -> usize;
|
||||||
|
fn tile_pos_x(&self, x: i32) -> u16 {
|
||||||
|
((x as u32) & (self.width() - 1)) as u16
|
||||||
|
}
|
||||||
|
fn tile_pos_y(&self, y: i32) -> u16 {
|
||||||
|
((y as u32) & (self.height() - 1)) as u16
|
||||||
|
}
|
||||||
|
fn px_offset_x(&self, x: i32) -> u16 {
|
||||||
|
((x as u32) & (self.width() * 8 - 1)) as u16
|
||||||
|
}
|
||||||
|
fn px_offset_y(&self, y: i32) -> u16 {
|
||||||
|
((y as u32) & (self.height() * 8 - 1)) as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackgroundSize for RegularBackgroundSize {
|
||||||
|
#[must_use]
|
||||||
|
fn width(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
RegularBackgroundSize::Background32x64 | RegularBackgroundSize::Background32x32 => 32,
|
RegularBackgroundSize::Background32x64 | RegularBackgroundSize::Background32x32 => 32,
|
||||||
RegularBackgroundSize::Background64x64 | RegularBackgroundSize::Background64x32 => 64,
|
RegularBackgroundSize::Background64x64 | RegularBackgroundSize::Background64x32 => 64,
|
||||||
|
@ -30,33 +78,26 @@ impl RegularBackgroundSize {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn height(&self) -> u32 {
|
fn height(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
RegularBackgroundSize::Background32x32 | RegularBackgroundSize::Background64x32 => 32,
|
RegularBackgroundSize::Background32x32 | RegularBackgroundSize::Background64x32 => 32,
|
||||||
RegularBackgroundSize::Background32x64 | RegularBackgroundSize::Background64x64 => 64,
|
RegularBackgroundSize::Background32x64 | RegularBackgroundSize::Background64x64 => 64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn size_flag(self) -> u16 {
|
impl BackgroundSizePrivate for RegularBackgroundSize {
|
||||||
match self {
|
fn size_flag(self) -> u16 {
|
||||||
RegularBackgroundSize::Background32x32 => 0,
|
self as u16
|
||||||
RegularBackgroundSize::Background64x32 => 1,
|
|
||||||
RegularBackgroundSize::Background32x64 => 2,
|
|
||||||
RegularBackgroundSize::Background64x64 => 3,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn num_tiles(self) -> usize {
|
fn num_screen_blocks(&self) -> usize {
|
||||||
(self.width() * self.height()) as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn num_screen_blocks(self) -> usize {
|
|
||||||
self.num_tiles() / (32 * 32)
|
self.num_tiles() / (32 * 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is hilariously complicated due to how the GBA stores the background screenblocks.
|
// This is hilariously complicated due to how the GBA stores the background screenblocks.
|
||||||
// See https://www.coranac.com/tonc/text/regbg.htm#sec-map for an explanation
|
// See https://www.coranac.com/tonc/text/regbg.htm#sec-map for an explanation
|
||||||
pub(crate) fn gba_offset(self, pos: Vector2D<u16>) -> usize {
|
fn gba_offset(&self, pos: Vector2D<u16>) -> usize {
|
||||||
let x_mod = pos.x & (self.width() as u16 - 1);
|
let x_mod = pos.x & (self.width() as u16 - 1);
|
||||||
let y_mod = pos.y & (self.height() as u16 - 1);
|
let y_mod = pos.y & (self.height() as u16 - 1);
|
||||||
|
|
||||||
|
@ -66,21 +107,43 @@ impl RegularBackgroundSize {
|
||||||
|
|
||||||
pos as usize
|
pos as usize
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn tile_pos_x(self, x: i32) -> u16 {
|
impl BackgroundSize for AffineBackgroundSize {
|
||||||
((x as u32) & (self.width() - 1)) as u16
|
#[must_use]
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
AffineBackgroundSize::Background16x16 => 16,
|
||||||
|
AffineBackgroundSize::Background32x32 => 32,
|
||||||
|
AffineBackgroundSize::Background64x64 => 64,
|
||||||
|
AffineBackgroundSize::Background128x128 => 128,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tile_pos_y(self, y: i32) -> u16 {
|
#[must_use]
|
||||||
((y as u32) & (self.height() - 1)) as u16
|
fn height(&self) -> u32 {
|
||||||
|
self.width()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BackgroundSizePrivate for AffineBackgroundSize {
|
||||||
|
fn size_flag(self) -> u16 {
|
||||||
|
self as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn px_offset_x(self, x: i32) -> u16 {
|
fn num_screen_blocks(&self) -> usize {
|
||||||
((x as u32) & (self.width() * 8 - 1)) as u16
|
// technically 16x16 and 32x32 only use the first 1/8 and 1/2 of the SB, respectively
|
||||||
|
1.max(self.num_tiles() / 2048)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn px_offset_y(self, y: i32) -> u16 {
|
// Affine modes don't do the convoluted staggered block layout
|
||||||
((y as u32) & (self.height() * 8 - 1)) as u16
|
fn gba_offset(&self, pos: Vector2D<u16>) -> usize {
|
||||||
|
let x_mod = pos.x & (self.width() as u16 - 1);
|
||||||
|
let y_mod = pos.y & (self.height() as u16 - 1);
|
||||||
|
|
||||||
|
let pos = x_mod + (self.width() as u16 * y_mod);
|
||||||
|
|
||||||
|
pos as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,11 +153,11 @@ struct Tile(u16);
|
||||||
|
|
||||||
impl Tile {
|
impl Tile {
|
||||||
fn new(idx: TileIndex, setting: TileSetting) -> Self {
|
fn new(idx: TileIndex, setting: TileSetting) -> Self {
|
||||||
Self(idx.index() | setting.setting())
|
Self(idx.raw_index() | setting.setting())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tile_index(self) -> TileIndex {
|
fn tile_index(self) -> TileIndex {
|
||||||
TileIndex::new(self.0 as usize & ((1 << 10) - 1))
|
TileIndex::new(self.0 as usize & ((1 << 10) - 1), TileFormat::FourBpp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +189,139 @@ impl TileSetting {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(self) fn find_screenblock_gap(screenblocks: &Bitarray<1>, gap: usize) -> usize {
|
||||||
|
let mut candidate = 0;
|
||||||
|
|
||||||
|
'outer: while candidate < 16 - gap {
|
||||||
|
let starting_point = candidate;
|
||||||
|
for attempt in starting_point..(starting_point + gap) {
|
||||||
|
if screenblocks.get(attempt) == Some(true) {
|
||||||
|
candidate = attempt + 1;
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!(
|
||||||
|
"Failed to find screenblock gap of at least {} elements",
|
||||||
|
gap
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TiledMode {
|
||||||
|
fn screenblocks(&self) -> &RefCell<Bitarray<1>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait CreatableRegularTiledMode: TiledMode {
|
||||||
|
const REGULAR_BACKGROUNDS: usize;
|
||||||
|
fn regular(&self) -> &RefCell<Bitarray<1>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait CreatableAffineTiledMode: TiledMode {
|
||||||
|
const AFFINE_BACKGROUNDS: usize;
|
||||||
|
fn affine(&self) -> &RefCell<Bitarray<1>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait RegularTiledMode {
|
||||||
|
fn regular_background(
|
||||||
|
&self,
|
||||||
|
priority: Priority,
|
||||||
|
size: RegularBackgroundSize,
|
||||||
|
) -> MapLoan<'_, RegularMap>;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait AffineTiledMode {
|
||||||
|
fn affine_background(
|
||||||
|
&self,
|
||||||
|
priority: Priority,
|
||||||
|
size: AffineBackgroundSize,
|
||||||
|
) -> MapLoan<'_, AffineMap>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RegularTiledMode for T
|
||||||
|
where
|
||||||
|
T: CreatableRegularTiledMode,
|
||||||
|
{
|
||||||
|
fn regular_background(
|
||||||
|
&self,
|
||||||
|
priority: Priority,
|
||||||
|
size: RegularBackgroundSize,
|
||||||
|
) -> MapLoan<'_, RegularMap> {
|
||||||
|
let mut regular = self.regular().borrow_mut();
|
||||||
|
let new_background = regular.first_zero().unwrap();
|
||||||
|
if new_background >= T::REGULAR_BACKGROUNDS {
|
||||||
|
panic!(
|
||||||
|
"can only have {} active regular backgrounds",
|
||||||
|
T::REGULAR_BACKGROUNDS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_screenblocks = size.num_screen_blocks();
|
||||||
|
let mut screenblocks = self.screenblocks().borrow_mut();
|
||||||
|
|
||||||
|
let screenblock = find_screenblock_gap(&screenblocks, num_screenblocks);
|
||||||
|
for id in screenblock..(screenblock + num_screenblocks) {
|
||||||
|
screenblocks.set(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bg = RegularMap::new(new_background as u8, screenblock as u8 + 16, priority, size);
|
||||||
|
|
||||||
|
regular.set(new_background, true);
|
||||||
|
|
||||||
|
MapLoan::new(
|
||||||
|
bg,
|
||||||
|
new_background as u8,
|
||||||
|
screenblock as u8,
|
||||||
|
num_screenblocks as u8,
|
||||||
|
self.regular(),
|
||||||
|
self.screenblocks(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AffineTiledMode for T
|
||||||
|
where
|
||||||
|
T: CreatableAffineTiledMode,
|
||||||
|
{
|
||||||
|
fn affine_background(
|
||||||
|
&self,
|
||||||
|
priority: Priority,
|
||||||
|
size: AffineBackgroundSize,
|
||||||
|
) -> MapLoan<'_, AffineMap> {
|
||||||
|
let mut affine = self.affine().borrow_mut();
|
||||||
|
let new_background = affine.first_zero().unwrap();
|
||||||
|
if new_background >= T::AFFINE_BACKGROUNDS + AFFINE_BG_ID_OFFSET {
|
||||||
|
panic!(
|
||||||
|
"can only have {} active affine backgrounds",
|
||||||
|
T::AFFINE_BACKGROUNDS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_screenblocks = size.num_screen_blocks();
|
||||||
|
let mut screenblocks = self.screenblocks().borrow_mut();
|
||||||
|
|
||||||
|
let screenblock = find_screenblock_gap(&screenblocks, num_screenblocks);
|
||||||
|
for id in screenblock..(screenblock + num_screenblocks) {
|
||||||
|
screenblocks.set(id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bg = AffineMap::new(new_background as u8, screenblock as u8 + 16, priority, size);
|
||||||
|
|
||||||
|
affine.set(new_background, true);
|
||||||
|
|
||||||
|
MapLoan::new(
|
||||||
|
bg,
|
||||||
|
new_background as u8,
|
||||||
|
screenblock as u8,
|
||||||
|
num_screenblocks as u8,
|
||||||
|
self.affine(),
|
||||||
|
self.screenblocks(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
CreatableRegularTiledMode, MapLoan, RegularBackgroundSize, RegularMap, RegularTiledMode,
|
||||||
|
TiledMode,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
bitarray::Bitarray,
|
bitarray::Bitarray,
|
||||||
display::{set_graphics_mode, DisplayMode, Priority},
|
display::{set_graphics_mode, DisplayMode, Priority},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{MapLoan, RegularBackgroundSize, RegularMap};
|
|
||||||
|
|
||||||
pub struct Tiled0 {
|
pub struct Tiled0 {
|
||||||
regular: RefCell<Bitarray<1>>,
|
regular: RefCell<Bitarray<1>>,
|
||||||
screenblocks: RefCell<Bitarray<1>>,
|
screenblocks: RefCell<Bitarray<1>>,
|
||||||
|
@ -27,52 +29,20 @@ impl Tiled0 {
|
||||||
priority: Priority,
|
priority: Priority,
|
||||||
size: RegularBackgroundSize,
|
size: RegularBackgroundSize,
|
||||||
) -> MapLoan<'_, RegularMap> {
|
) -> MapLoan<'_, RegularMap> {
|
||||||
let mut regular = self.regular.borrow_mut();
|
self.regular_background(priority, size)
|
||||||
let new_background = regular.first_zero().unwrap();
|
|
||||||
if new_background >= 4 {
|
|
||||||
panic!("can only have 4 active backgrounds");
|
|
||||||
}
|
|
||||||
|
|
||||||
let num_screenblocks = size.num_screen_blocks();
|
|
||||||
let mut screenblocks = self.screenblocks.borrow_mut();
|
|
||||||
|
|
||||||
let screenblock = find_screenblock_gap(&screenblocks, num_screenblocks);
|
|
||||||
for id in screenblock..(screenblock + num_screenblocks) {
|
|
||||||
screenblocks.set(id, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let bg = RegularMap::new(new_background as u8, screenblock as u8 + 16, priority, size);
|
|
||||||
|
|
||||||
regular.set(new_background, true);
|
|
||||||
|
|
||||||
MapLoan::new(
|
|
||||||
bg,
|
|
||||||
new_background as u8,
|
|
||||||
screenblock as u8,
|
|
||||||
num_screenblocks as u8,
|
|
||||||
&self.regular,
|
|
||||||
&self.screenblocks,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_screenblock_gap(screenblocks: &Bitarray<1>, gap: usize) -> usize {
|
impl TiledMode for Tiled0 {
|
||||||
let mut candidate = 0;
|
fn screenblocks(&self) -> &RefCell<Bitarray<1>> {
|
||||||
|
&self.screenblocks
|
||||||
'outer: while candidate < 16 - gap {
|
}
|
||||||
let starting_point = candidate;
|
}
|
||||||
for attempt in starting_point..(starting_point + gap) {
|
|
||||||
if screenblocks.get(attempt) == Some(true) {
|
impl CreatableRegularTiledMode for Tiled0 {
|
||||||
candidate = attempt + 1;
|
const REGULAR_BACKGROUNDS: usize = 4;
|
||||||
continue 'outer;
|
|
||||||
}
|
fn regular(&self) -> &RefCell<Bitarray<1>> {
|
||||||
}
|
&self.regular
|
||||||
|
|
||||||
return candidate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
panic!(
|
|
||||||
"Failed to find screenblock gap of at least {} elements",
|
|
||||||
gap
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
52
agb/src/display/tiled/tiled1.rs
Normal file
52
agb/src/display/tiled/tiled1.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
use super::{CreatableAffineTiledMode, CreatableRegularTiledMode, TiledMode};
|
||||||
|
use crate::{
|
||||||
|
bitarray::Bitarray,
|
||||||
|
display::{set_graphics_mode, tiled::AFFINE_BG_ID_OFFSET, DisplayMode},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Tiled1 {
|
||||||
|
regular: RefCell<Bitarray<1>>,
|
||||||
|
affine: RefCell<Bitarray<1>>,
|
||||||
|
screenblocks: RefCell<Bitarray<1>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tiled1 {
|
||||||
|
pub(crate) unsafe fn new() -> Self {
|
||||||
|
set_graphics_mode(DisplayMode::Tiled1);
|
||||||
|
|
||||||
|
let affine = RefCell::new(Bitarray::new());
|
||||||
|
for i in 0..AFFINE_BG_ID_OFFSET {
|
||||||
|
affine.borrow_mut().set(i, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
regular: Default::default(),
|
||||||
|
affine,
|
||||||
|
screenblocks: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiledMode for Tiled1 {
|
||||||
|
fn screenblocks(&self) -> &RefCell<Bitarray<1>> {
|
||||||
|
&self.screenblocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreatableRegularTiledMode for Tiled1 {
|
||||||
|
const REGULAR_BACKGROUNDS: usize = 2;
|
||||||
|
|
||||||
|
fn regular(&self) -> &RefCell<Bitarray<1>> {
|
||||||
|
&self.regular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreatableAffineTiledMode for Tiled1 {
|
||||||
|
const AFFINE_BACKGROUNDS: usize = 1;
|
||||||
|
|
||||||
|
fn affine(&self) -> &RefCell<Bitarray<1>> {
|
||||||
|
&self.affine
|
||||||
|
}
|
||||||
|
}
|
52
agb/src/display/tiled/tiled2.rs
Normal file
52
agb/src/display/tiled/tiled2.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
AffineBackgroundSize, AffineMap, AffineTiledMode, CreatableAffineTiledMode, MapLoan, TiledMode,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
bitarray::Bitarray,
|
||||||
|
display::{set_graphics_mode, tiled::AFFINE_BG_ID_OFFSET, DisplayMode, Priority},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Tiled2 {
|
||||||
|
affine: RefCell<Bitarray<1>>,
|
||||||
|
screenblocks: RefCell<Bitarray<1>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tiled2 {
|
||||||
|
pub(crate) unsafe fn new() -> Self {
|
||||||
|
set_graphics_mode(DisplayMode::Tiled2);
|
||||||
|
|
||||||
|
let affine = RefCell::new(Bitarray::new());
|
||||||
|
for i in 0..AFFINE_BG_ID_OFFSET {
|
||||||
|
affine.borrow_mut().set(i, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
affine,
|
||||||
|
screenblocks: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn background(
|
||||||
|
&self,
|
||||||
|
priority: Priority,
|
||||||
|
size: AffineBackgroundSize,
|
||||||
|
) -> MapLoan<'_, AffineMap> {
|
||||||
|
self.affine_background(priority, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiledMode for Tiled2 {
|
||||||
|
fn screenblocks(&self) -> &RefCell<Bitarray<1>> {
|
||||||
|
&self.screenblocks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreatableAffineTiledMode for Tiled2 {
|
||||||
|
const AFFINE_BACKGROUNDS: usize = 2;
|
||||||
|
|
||||||
|
fn affine(&self) -> &RefCell<Bitarray<1>> {
|
||||||
|
&self.affine
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ use core::{alloc::Layout, ptr::NonNull};
|
||||||
|
|
||||||
use alloc::{slice, vec::Vec};
|
use alloc::{slice, vec::Vec};
|
||||||
|
|
||||||
|
use crate::display::tiled::Tile;
|
||||||
use crate::{
|
use crate::{
|
||||||
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
|
agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd},
|
||||||
display::palette16,
|
display::palette16,
|
||||||
|
@ -22,18 +23,22 @@ static TILE_ALLOCATOR: BlockAllocator = unsafe {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const TILE_LAYOUT: Layout = unsafe { Layout::from_size_align_unchecked(8 * 8 / 2, 8 * 8 / 2) };
|
const fn layout_of(format: TileFormat) -> Layout {
|
||||||
|
unsafe { Layout::from_size_align_unchecked(format.tile_size(), format.tile_size()) }
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum TileFormat {
|
pub enum TileFormat {
|
||||||
FourBpp,
|
FourBpp,
|
||||||
|
EightBpp,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TileFormat {
|
impl TileFormat {
|
||||||
/// Returns the size of the tile in bytes
|
/// Returns the size of the tile in bytes
|
||||||
fn tile_size(self) -> usize {
|
pub(crate) const fn tile_size(self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
TileFormat::FourBpp => 8 * 8 / 2,
|
TileFormat::FourBpp => 8 * 8 / 2,
|
||||||
|
TileFormat::EightBpp => 8 * 8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,15 +60,50 @@ impl<'a> TileSet<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct TileIndex(u16);
|
pub enum TileIndex {
|
||||||
|
FourBpp(u16),
|
||||||
|
EightBpp(u8),
|
||||||
|
}
|
||||||
|
|
||||||
impl TileIndex {
|
impl TileIndex {
|
||||||
pub(crate) const fn new(index: usize) -> Self {
|
pub(crate) const fn new(index: usize, format: TileFormat) -> Self {
|
||||||
Self(index as u16)
|
match format {
|
||||||
|
TileFormat::FourBpp => Self::FourBpp(index as u16),
|
||||||
|
TileFormat::EightBpp => Self::EightBpp(index as u8),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn index(self) -> u16 {
|
pub(crate) const fn raw_index(self) -> u16 {
|
||||||
self.0
|
match self {
|
||||||
|
TileIndex::FourBpp(x) => x,
|
||||||
|
TileIndex::EightBpp(x) => x as u16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn format(self) -> TileFormat {
|
||||||
|
match self {
|
||||||
|
TileIndex::FourBpp(_) => TileFormat::FourBpp,
|
||||||
|
TileIndex::EightBpp(_) => TileFormat::EightBpp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refcount_key(self) -> usize {
|
||||||
|
match self {
|
||||||
|
TileIndex::FourBpp(x) => x as usize,
|
||||||
|
TileIndex::EightBpp(x) => x as usize * 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Tile> for TileIndex {
|
||||||
|
fn from(tile: Tile) -> Self {
|
||||||
|
tile.tile_index()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for TileIndex {
|
||||||
|
fn from(index: u8) -> TileIndex {
|
||||||
|
TileIndex::new(usize::from(index), TileFormat::EightBpp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +199,7 @@ impl DynamicTile<'_> {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn tile_index(&self) -> u16 {
|
pub fn tile_index(&self) -> u16 {
|
||||||
let difference = self.tile_data.as_ptr() as usize - TILE_RAM_START;
|
let difference = self.tile_data.as_ptr() as usize - TILE_RAM_START;
|
||||||
(difference / (8 * 8 / 2)) as u16
|
(difference / TileFormat::FourBpp.tile_size()) as u16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,24 +222,27 @@ impl VRamManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_from_reference(reference: TileReference) -> usize {
|
fn index_from_reference(reference: TileReference, format: TileFormat) -> TileIndex {
|
||||||
let difference = reference.0.as_ptr() as usize - TILE_RAM_START;
|
let difference = reference.0.as_ptr() as usize - TILE_RAM_START;
|
||||||
difference / (8 * 8 / 2)
|
TileIndex::new(difference / format.tile_size(), format)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reference_from_index(index: TileIndex) -> TileReference {
|
fn reference_from_index(index: TileIndex) -> TileReference {
|
||||||
let ptr = (index.index() * (8 * 8 / 2)) as usize + TILE_RAM_START;
|
let ptr = (index.raw_index() as usize * index.format().tile_size()) + TILE_RAM_START;
|
||||||
TileReference(NonNull::new(ptr as *mut _).unwrap())
|
TileReference(NonNull::new(ptr as *mut _).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new_dynamic_tile<'a>(&mut self) -> DynamicTile<'a> {
|
pub fn new_dynamic_tile<'a>(&mut self) -> DynamicTile<'a> {
|
||||||
|
// TODO: format param?
|
||||||
let tile_format = TileFormat::FourBpp;
|
let tile_format = TileFormat::FourBpp;
|
||||||
let new_reference: NonNull<u32> =
|
let new_reference: NonNull<u32> = unsafe { TILE_ALLOCATOR.alloc(layout_of(tile_format)) }
|
||||||
unsafe { TILE_ALLOCATOR.alloc(TILE_LAYOUT) }.unwrap().cast();
|
.unwrap()
|
||||||
|
.cast();
|
||||||
let tile_reference = TileReference(new_reference);
|
let tile_reference = TileReference(new_reference);
|
||||||
|
|
||||||
let index = Self::index_from_reference(tile_reference);
|
let index = Self::index_from_reference(tile_reference, tile_format);
|
||||||
|
let key = index.refcount_key();
|
||||||
|
|
||||||
let tiles = unsafe {
|
let tiles = unsafe {
|
||||||
slice::from_raw_parts_mut(TILE_RAM_START as *mut u8, 1024 * tile_format.tile_size())
|
slice::from_raw_parts_mut(TILE_RAM_START as *mut u8, 1024 * tile_format.tile_size())
|
||||||
|
@ -208,23 +251,21 @@ impl VRamManager {
|
||||||
let tile_set = TileSet::new(tiles, tile_format);
|
let tile_set = TileSet::new(tiles, tile_format);
|
||||||
|
|
||||||
self.tile_set_to_vram.insert(
|
self.tile_set_to_vram.insert(
|
||||||
TileInTileSetReference::new(&tile_set, index as u16),
|
TileInTileSetReference::new(&tile_set, index.raw_index()),
|
||||||
tile_reference,
|
tile_reference,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.reference_counts.resize(
|
self.reference_counts
|
||||||
self.reference_counts.len().max(index + 1),
|
.resize(self.reference_counts.len().max(key + 1), Default::default());
|
||||||
Default::default(),
|
self.reference_counts[key] =
|
||||||
);
|
TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index.raw_index()));
|
||||||
self.reference_counts[index] =
|
|
||||||
TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index as u16));
|
|
||||||
|
|
||||||
DynamicTile {
|
DynamicTile {
|
||||||
tile_data: unsafe {
|
tile_data: unsafe {
|
||||||
slice::from_raw_parts_mut(
|
slice::from_raw_parts_mut(
|
||||||
tiles
|
tiles
|
||||||
.as_mut_ptr()
|
.as_mut_ptr()
|
||||||
.add((index * tile_format.tile_size()) as usize)
|
.add(index.raw_index() as usize * tile_format.tile_size())
|
||||||
.cast(),
|
.cast(),
|
||||||
tile_format.tile_size() / core::mem::size_of::<u32>(),
|
tile_format.tile_size() / core::mem::size_of::<u32>(),
|
||||||
)
|
)
|
||||||
|
@ -238,8 +279,9 @@ impl VRamManager {
|
||||||
let pointer = NonNull::new(dynamic_tile.tile_data.as_mut_ptr() as *mut _).unwrap();
|
let pointer = NonNull::new(dynamic_tile.tile_data.as_mut_ptr() as *mut _).unwrap();
|
||||||
let tile_reference = TileReference(pointer);
|
let tile_reference = TileReference(pointer);
|
||||||
|
|
||||||
let tile_index = Self::index_from_reference(tile_reference);
|
// TODO: dynamic_tile.format?
|
||||||
self.remove_tile(TileIndex::new(tile_index));
|
let tile_index = Self::index_from_reference(tile_reference, TileFormat::FourBpp);
|
||||||
|
self.remove_tile(tile_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_tile(&mut self, tile_set: &TileSet<'_>, tile: u16) -> TileIndex {
|
pub(crate) fn add_tile(&mut self, tile_set: &TileSet<'_>, tile: u16) -> TileIndex {
|
||||||
|
@ -248,37 +290,39 @@ impl VRamManager {
|
||||||
.get(&TileInTileSetReference::new(tile_set, tile));
|
.get(&TileInTileSetReference::new(tile_set, tile));
|
||||||
|
|
||||||
if let Some(reference) = reference {
|
if let Some(reference) = reference {
|
||||||
let index = Self::index_from_reference(*reference);
|
let tile_index = Self::index_from_reference(*reference, tile_set.format);
|
||||||
self.reference_counts[index].increment_reference_count();
|
let key = tile_index.refcount_key();
|
||||||
return TileIndex::new(index);
|
self.reference_counts[key].increment_reference_count();
|
||||||
|
return tile_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_reference: NonNull<u32> =
|
let new_reference: NonNull<u32> =
|
||||||
unsafe { TILE_ALLOCATOR.alloc(TILE_LAYOUT) }.unwrap().cast();
|
unsafe { TILE_ALLOCATOR.alloc(layout_of(tile_set.format)) }
|
||||||
|
.unwrap()
|
||||||
|
.cast();
|
||||||
let tile_reference = TileReference(new_reference);
|
let tile_reference = TileReference(new_reference);
|
||||||
|
|
||||||
self.copy_tile_to_location(tile_set, tile, tile_reference);
|
self.copy_tile_to_location(tile_set, tile, tile_reference);
|
||||||
|
|
||||||
let index = Self::index_from_reference(tile_reference);
|
let index = Self::index_from_reference(tile_reference, tile_set.format);
|
||||||
|
let key = index.refcount_key();
|
||||||
|
|
||||||
self.tile_set_to_vram
|
self.tile_set_to_vram
|
||||||
.insert(TileInTileSetReference::new(tile_set, tile), tile_reference);
|
.insert(TileInTileSetReference::new(tile_set, tile), tile_reference);
|
||||||
|
|
||||||
self.reference_counts.resize(
|
self.reference_counts
|
||||||
self.reference_counts.len().max(index + 1),
|
.resize(self.reference_counts.len().max(key + 1), Default::default());
|
||||||
Default::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.reference_counts[index] =
|
self.reference_counts[key] =
|
||||||
TileReferenceCount::new(TileInTileSetReference::new(tile_set, tile));
|
TileReferenceCount::new(TileInTileSetReference::new(tile_set, tile));
|
||||||
|
|
||||||
TileIndex::new(index)
|
index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove_tile(&mut self, tile_index: TileIndex) {
|
pub(crate) fn remove_tile(&mut self, tile_index: TileIndex) {
|
||||||
let index = tile_index.index() as usize;
|
let key = tile_index.refcount_key();
|
||||||
|
|
||||||
let new_reference_count = self.reference_counts[index].decrement_reference_count();
|
let new_reference_count = self.reference_counts[key].decrement_reference_count();
|
||||||
|
|
||||||
if new_reference_count != 0 {
|
if new_reference_count != 0 {
|
||||||
return;
|
return;
|
||||||
|
@ -289,23 +333,26 @@ impl VRamManager {
|
||||||
|
|
||||||
pub(crate) fn gc(&mut self) {
|
pub(crate) fn gc(&mut self) {
|
||||||
for tile_index in self.indices_to_gc.drain(..) {
|
for tile_index in self.indices_to_gc.drain(..) {
|
||||||
let index = tile_index.index() as usize;
|
let key = tile_index.refcount_key();
|
||||||
if self.reference_counts[index].current_count() > 0 {
|
if self.reference_counts[key].current_count() > 0 {
|
||||||
continue; // it has since been added back
|
continue; // it has since been added back
|
||||||
}
|
}
|
||||||
|
|
||||||
let tile_reference = Self::reference_from_index(tile_index);
|
let tile_reference = Self::reference_from_index(tile_index);
|
||||||
unsafe {
|
unsafe {
|
||||||
TILE_ALLOCATOR.dealloc_no_normalise(tile_reference.0.cast().as_ptr(), TILE_LAYOUT);
|
TILE_ALLOCATOR.dealloc_no_normalise(
|
||||||
|
tile_reference.0.cast().as_ptr(),
|
||||||
|
layout_of(tile_index.format()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tile_ref = self.reference_counts[index]
|
let tile_ref = self.reference_counts[key]
|
||||||
.tile_in_tile_set
|
.tile_in_tile_set
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.tile_set_to_vram.remove(tile_ref);
|
self.tile_set_to_vram.remove(tile_ref);
|
||||||
self.reference_counts[index].clear();
|
self.reference_counts[key].clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,6 +363,11 @@ impl VRamManager {
|
||||||
target_tile_set: &TileSet<'_>,
|
target_tile_set: &TileSet<'_>,
|
||||||
target_tile: u16,
|
target_tile: u16,
|
||||||
) {
|
) {
|
||||||
|
assert_eq!(
|
||||||
|
source_tile_set.format, target_tile_set.format,
|
||||||
|
"Must replace a tileset with the same format"
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(&reference) = self
|
if let Some(&reference) = self
|
||||||
.tile_set_to_vram
|
.tile_set_to_vram
|
||||||
.get(&TileInTileSetReference::new(source_tile_set, source_tile))
|
.get(&TileInTileSetReference::new(source_tile_set, source_tile))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{
|
use super::{
|
||||||
bitmap3::Bitmap3,
|
bitmap3::Bitmap3,
|
||||||
bitmap4::Bitmap4,
|
bitmap4::Bitmap4,
|
||||||
tiled::{Tiled0, VRamManager},
|
tiled::{Tiled0, Tiled1, Tiled2, VRamManager},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The video struct controls access to the video hardware.
|
/// The video struct controls access to the video hardware.
|
||||||
|
@ -26,4 +26,14 @@ impl Video {
|
||||||
pub fn tiled0(&mut self) -> (Tiled0, VRamManager) {
|
pub fn tiled0(&mut self) -> (Tiled0, VRamManager) {
|
||||||
(unsafe { Tiled0::new() }, VRamManager::new())
|
(unsafe { Tiled0::new() }, VRamManager::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tiled 1 mode provides 2 regular tiled backgrounds and 1 affine tiled background
|
||||||
|
pub fn tiled1(&mut self) -> (Tiled1, VRamManager) {
|
||||||
|
(unsafe { Tiled1::new() }, VRamManager::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tiled 2 mode provides 2 affine tiled backgrounds
|
||||||
|
pub fn tiled2(&mut self) -> (Tiled2, VRamManager) {
|
||||||
|
(unsafe { Tiled2::new() }, VRamManager::new())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
/// #
|
/// #
|
||||||
/// use agb::{
|
/// use agb::{
|
||||||
/// display::{
|
/// display::{
|
||||||
/// tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, VRamManager},
|
/// tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, TiledMap, VRamManager},
|
||||||
/// Priority,
|
/// Priority,
|
||||||
/// },
|
/// },
|
||||||
/// include_gfx,
|
/// include_gfx,
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
/// # fn load_tileset(mut gfx: Tiled0, mut vram: VRamManager) {
|
/// # fn load_tileset(mut gfx: Tiled0, mut vram: VRamManager) {
|
||||||
/// let tileset = TileSet::new(water_tiles::water_tiles.tiles, TileFormat::FourBpp);
|
/// let tileset = TileSet::new(water_tiles::water_tiles.tiles, TileFormat::FourBpp);
|
||||||
///
|
///
|
||||||
/// vram.set_background_palettes(water_tiles::water_tiles.palettes);
|
/// vram.set_background_palettes(water_tiles::PALETTES);
|
||||||
///
|
///
|
||||||
/// let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
/// let mut bg = gfx.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
||||||
///
|
///
|
||||||
|
|
|
@ -16,7 +16,9 @@ impl<T> MemoryMapped<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&self, val: T) {
|
pub fn set(&self, val: T) {
|
||||||
unsafe { self.address.write_volatile(val) }
|
if core::mem::size_of::<T>() != 0 {
|
||||||
|
unsafe { self.address.write_volatile(val) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ fn set_bank(bank: u8) -> Result<(), Error> {
|
||||||
Err(Error::OutOfBounds)
|
Err(Error::OutOfBounds)
|
||||||
} else if bank != CURRENT_BANK.read() {
|
} else if bank != CURRENT_BANK.read() {
|
||||||
issue_flash_command(CMD_SET_BANK);
|
issue_flash_command(CMD_SET_BANK);
|
||||||
FLASH_PORT_BANK.set(bank as u8);
|
FLASH_PORT_BANK.set(bank);
|
||||||
CURRENT_BANK.write(bank);
|
CURRENT_BANK.write(bank);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -14,17 +14,11 @@
|
||||||
//!
|
//!
|
||||||
//! # Concepts
|
//! # Concepts
|
||||||
//!
|
//!
|
||||||
//! The mixer runs at a fixed frequency which is determined at compile time by enabling
|
//! The mixer runs at a fixed frequency which is determined at initialisation time by
|
||||||
//! certain features within the crate. The following features are currently available:
|
//! passing certain [`Frequency`] options.
|
||||||
//!
|
|
||||||
//! | Feature | Frequency |
|
|
||||||
//! |---------|-----------|
|
|
||||||
//! | none | 10512Hz |
|
|
||||||
//! | freq18157 | 18157Hz |
|
|
||||||
//! | freq32768[^32768Hz] | 32768Hz |
|
|
||||||
//!
|
//!
|
||||||
//! All wav files you use within your application / game must use this _exact_ frequency.
|
//! All wav files you use within your application / game must use this _exact_ frequency.
|
||||||
//! You will get a compile error if you use the incorrect frequency for your file.
|
//! If you don't use this frequency, the sound will play either too slowly or too quickly.
|
||||||
//!
|
//!
|
||||||
//! The mixer can play both mono and stereo sounds, but only mono sound effects can have
|
//! The mixer can play both mono and stereo sounds, but only mono sound effects can have
|
||||||
//! effects applied to them (such as changing the speed at which they play or the panning).
|
//! effects applied to them (such as changing the speed at which they play or the panning).
|
||||||
|
@ -38,12 +32,17 @@
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! # #![no_std]
|
//! # #![no_std]
|
||||||
//! # #![no_main]
|
//! # #![no_main]
|
||||||
|
//! use agb::sound::mixer::Frequency;
|
||||||
//! # fn foo(gba: &mut agb::Gba) {
|
//! # fn foo(gba: &mut agb::Gba) {
|
||||||
//! let mut mixer = gba.mixer.mixer();
|
//! let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
//! mixer.enable();
|
//! mixer.enable();
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! Pass a frequency option. This option must be used for the entire lifetime of the `mixer`
|
||||||
|
//! variable. If you want to change frequency, you will need to drop this one and create a new
|
||||||
|
//! one.
|
||||||
|
//!
|
||||||
//! ## Doing the per-frame work
|
//! ## Doing the per-frame work
|
||||||
//!
|
//!
|
||||||
//! Then, you have a choice of whether you want to use interrupts or do the buffer swapping
|
//! Then, you have a choice of whether you want to use interrupts or do the buffer swapping
|
||||||
|
@ -55,9 +54,10 @@
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! # #![no_std]
|
//! # #![no_std]
|
||||||
//! # #![no_main]
|
//! # #![no_main]
|
||||||
|
//! use agb::sound::mixer::Frequency;
|
||||||
//! # fn foo(gba: &mut agb::Gba) {
|
//! # fn foo(gba: &mut agb::Gba) {
|
||||||
//! # let mut mixer = gba.mixer.mixer();
|
//! let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
//! # let vblank = agb::interrupt::VBlank::get();
|
//! let vblank = agb::interrupt::VBlank::get();
|
||||||
//! // Somewhere in your main loop:
|
//! // Somewhere in your main loop:
|
||||||
//! mixer.frame();
|
//! mixer.frame();
|
||||||
//! vblank.wait_for_vblank();
|
//! vblank.wait_for_vblank();
|
||||||
|
@ -70,10 +70,13 @@
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! # #![no_std]
|
//! # #![no_std]
|
||||||
//! # #![no_main]
|
//! # #![no_main]
|
||||||
|
//! use agb::sound::mixer::Frequency;
|
||||||
//! # fn foo(gba: &mut agb::Gba) {
|
//! # fn foo(gba: &mut agb::Gba) {
|
||||||
//! # let mut mixer = gba.mixer.mixer();
|
//! let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz32768);
|
||||||
//! # let vblank = agb::interrupt::VBlank::get();
|
//! let vblank = agb::interrupt::VBlank::get();
|
||||||
//! // outside your main loop, close to initialisation
|
//! // outside your main loop, close to initialisation
|
||||||
|
//! // you must assign this to a variable (not to _ or ignored) or rust will immediately drop it
|
||||||
|
//! // and prevent the interrupt handler from firing.
|
||||||
//! let _mixer_interrupt = mixer.setup_interrupt_handler();
|
//! let _mixer_interrupt = mixer.setup_interrupt_handler();
|
||||||
//!
|
//!
|
||||||
//! // inside your main loop
|
//! // inside your main loop
|
||||||
|
@ -99,7 +102,7 @@
|
||||||
//! # #![no_std]
|
//! # #![no_std]
|
||||||
//! # #![no_main]
|
//! # #![no_main]
|
||||||
//! # fn foo(gba: &mut agb::Gba) {
|
//! # fn foo(gba: &mut agb::Gba) {
|
||||||
//! # let mut mixer = gba.mixer.mixer();
|
//! # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
//! # let vblank = agb::interrupt::VBlank::get();
|
//! # let vblank = agb::interrupt::VBlank::get();
|
||||||
//! # use agb::{*, sound::mixer::*};
|
//! # use agb::{*, sound::mixer::*};
|
||||||
//! // Outside your main function in global scope:
|
//! // Outside your main function in global scope:
|
||||||
|
@ -116,9 +119,6 @@
|
||||||
//!
|
//!
|
||||||
//! Once you have run [`play_sound`](Mixer::play_sound), the mixer will play that sound until
|
//! Once you have run [`play_sound`](Mixer::play_sound), the mixer will play that sound until
|
||||||
//! it has finished.
|
//! it has finished.
|
||||||
//!
|
|
||||||
//! [^32768Hz]: You must use interrupts when using 32768Hz
|
|
||||||
|
|
||||||
mod hw;
|
mod hw;
|
||||||
mod sw_mixer;
|
mod sw_mixer;
|
||||||
|
|
||||||
|
@ -138,8 +138,8 @@ impl MixerController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a [`Mixer`] in order to start producing sounds.
|
/// Get a [`Mixer`] in order to start producing sounds.
|
||||||
pub fn mixer(&mut self) -> Mixer {
|
pub fn mixer(&mut self, frequency: Frequency) -> Mixer {
|
||||||
Mixer::new()
|
Mixer::new(frequency)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +149,43 @@ enum SoundPriority {
|
||||||
Low,
|
Low,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The supported frequencies within AGB. These are chosen to work well with
|
||||||
|
/// the hardware. Note that the higher the frequency, the better the quality of
|
||||||
|
/// the sound but the more CPU time sound mixing will take.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Frequency {
|
||||||
|
/// 10512Hz
|
||||||
|
Hz10512,
|
||||||
|
/// 18157Hz
|
||||||
|
Hz18157,
|
||||||
|
/// 32768Hz - note that this option requires interrupts for buffer swapping
|
||||||
|
Hz32768,
|
||||||
|
}
|
||||||
|
|
||||||
|
// list here: http://deku.gbadev.org/program/sound1.html
|
||||||
|
impl Frequency {
|
||||||
|
pub(crate) fn frequency(self) -> i32 {
|
||||||
|
use Frequency::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Hz10512 => 10512,
|
||||||
|
Hz18157 => 18157,
|
||||||
|
Hz32768 => 32768,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn buffer_size(self) -> usize {
|
||||||
|
use Frequency::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Hz10512 => 176,
|
||||||
|
Hz18157 => 304,
|
||||||
|
Hz32768 => 560,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Describes one sound which should be playing. This could be a sound effect or
|
/// Describes one sound which should be playing. This could be a sound effect or
|
||||||
/// the background music. Use the factory methods on this to modify how it is played.
|
/// the background music. Use the factory methods on this to modify how it is played.
|
||||||
///
|
///
|
||||||
|
@ -185,7 +222,7 @@ enum SoundPriority {
|
||||||
///
|
///
|
||||||
/// // somewhere in code
|
/// // somewhere in code
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// let mut bgm = SoundChannel::new_high_priority(MY_BGM);
|
/// let mut bgm = SoundChannel::new_high_priority(MY_BGM);
|
||||||
/// bgm.stereo().should_loop();
|
/// bgm.stereo().should_loop();
|
||||||
/// let _ = mixer.play_sound(bgm);
|
/// let _ = mixer.play_sound(bgm);
|
||||||
|
@ -204,7 +241,7 @@ enum SoundPriority {
|
||||||
///
|
///
|
||||||
/// // somewhere in code
|
/// // somewhere in code
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// let jump_sound = SoundChannel::new(JUMP_SOUND);
|
/// let jump_sound = SoundChannel::new(JUMP_SOUND);
|
||||||
/// let _ = mixer.play_sound(jump_sound);
|
/// let _ = mixer.play_sound(jump_sound);
|
||||||
/// # }
|
/// # }
|
||||||
|
@ -241,7 +278,7 @@ impl SoundChannel {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// // in global scope:
|
/// // in global scope:
|
||||||
/// const JUMP_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav");
|
/// const JUMP_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav");
|
||||||
///
|
///
|
||||||
|
@ -283,7 +320,7 @@ impl SoundChannel {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// // in global scope:
|
/// // in global scope:
|
||||||
/// const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
/// const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use core::cell::RefCell;
|
use core::cell::RefCell;
|
||||||
use core::intrinsics::transmute;
|
|
||||||
|
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use alloc::vec::Vec;
|
||||||
use bare_metal::{CriticalSection, Mutex};
|
use bare_metal::{CriticalSection, Mutex};
|
||||||
|
|
||||||
use super::hw;
|
|
||||||
use super::hw::LeftOrRight;
|
use super::hw::LeftOrRight;
|
||||||
|
use super::{hw, Frequency};
|
||||||
use super::{SoundChannel, SoundPriority};
|
use super::{SoundChannel, SoundPriority};
|
||||||
|
|
||||||
|
use crate::InternalAllocator;
|
||||||
use crate::{
|
use crate::{
|
||||||
fixnum::Num,
|
fixnum::Num,
|
||||||
interrupt::free,
|
interrupt::free,
|
||||||
|
@ -46,7 +48,7 @@ extern "C" {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// let mut mixer = gba.mixer.mixer();
|
/// let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
@ -58,13 +60,13 @@ extern "C" {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// // Outside your main function in global scope:
|
/// // Outside your main function in global scope:
|
||||||
/// const MY_CRAZY_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav");
|
/// const MY_CRAZY_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav");
|
||||||
///
|
///
|
||||||
/// // in your main function:
|
/// // in your main function:
|
||||||
/// let mut mixer = gba.mixer.mixer();
|
/// let mut mixer = gba.mixer.mixer(Frequency::Hz10512);
|
||||||
/// let mut channel = SoundChannel::new(MY_CRAZY_SOUND);
|
/// let mut channel = SoundChannel::new(MY_CRAZY_SOUND);
|
||||||
/// channel.stereo();
|
/// channel.stereo();
|
||||||
/// let _ = mixer.play_sound(channel);
|
/// let _ = mixer.play_sound(channel);
|
||||||
|
@ -80,6 +82,7 @@ pub struct Mixer {
|
||||||
buffer: MixerBuffer,
|
buffer: MixerBuffer,
|
||||||
channels: [Option<SoundChannel>; 8],
|
channels: [Option<SoundChannel>; 8],
|
||||||
indices: [i32; 8],
|
indices: [i32; 8],
|
||||||
|
frequency: Frequency,
|
||||||
|
|
||||||
timer: Timer,
|
timer: Timer,
|
||||||
}
|
}
|
||||||
|
@ -96,7 +99,7 @@ pub struct Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
||||||
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
||||||
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
@ -108,9 +111,10 @@ pub struct Mixer {
|
||||||
pub struct ChannelId(usize, i32);
|
pub struct ChannelId(usize, i32);
|
||||||
|
|
||||||
impl Mixer {
|
impl Mixer {
|
||||||
pub(super) fn new() -> Self {
|
pub(super) fn new(frequency: Frequency) -> Self {
|
||||||
Self {
|
Self {
|
||||||
buffer: MixerBuffer::new(),
|
frequency,
|
||||||
|
buffer: MixerBuffer::new(frequency),
|
||||||
channels: Default::default(),
|
channels: Default::default(),
|
||||||
indices: Default::default(),
|
indices: Default::default(),
|
||||||
|
|
||||||
|
@ -123,7 +127,7 @@ impl Mixer {
|
||||||
/// You must call this method in order to start playing sound. You can do as much set up before
|
/// You must call this method in order to start playing sound. You can do as much set up before
|
||||||
/// this as you like, but you will not get any sound out of the console until this method is called.
|
/// this as you like, but you will not get any sound out of the console until this method is called.
|
||||||
pub fn enable(&mut self) {
|
pub fn enable(&mut self) {
|
||||||
hw::set_timer_counter_for_frequency_and_enable(&mut self.timer, constants::SOUND_FREQUENCY);
|
hw::set_timer_counter_for_frequency_and_enable(&mut self.timer, self.frequency.frequency());
|
||||||
hw::set_sound_control_register_for_mixer();
|
hw::set_sound_control_register_for_mixer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +142,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// loop {
|
/// loop {
|
||||||
/// mixer.frame();
|
/// mixer.frame();
|
||||||
|
@ -167,7 +171,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// // you must set this to a named variable to ensure that the scope is long enough
|
/// // you must set this to a named variable to ensure that the scope is long enough
|
||||||
/// let _mixer_interrupt = mixer.setup_interrupt_handler();
|
/// let _mixer_interrupt = mixer.setup_interrupt_handler();
|
||||||
|
@ -184,7 +188,7 @@ impl Mixer {
|
||||||
.set_cascade(true)
|
.set_cascade(true)
|
||||||
.set_divider(Divider::Divider1)
|
.set_divider(Divider::Divider1)
|
||||||
.set_interrupt(true)
|
.set_interrupt(true)
|
||||||
.set_overflow_amount(constants::SOUND_BUFFER_SIZE as u16)
|
.set_overflow_amount(self.frequency.buffer_size() as u16)
|
||||||
.set_enabled(true);
|
.set_enabled(true);
|
||||||
|
|
||||||
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
|
add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs))
|
||||||
|
@ -205,7 +209,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # let vblank = agb::interrupt::VBlank::get();
|
/// # let vblank = agb::interrupt::VBlank::get();
|
||||||
/// loop {
|
/// loop {
|
||||||
/// mixer.frame();
|
/// mixer.frame();
|
||||||
|
@ -244,7 +248,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
||||||
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
||||||
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
@ -293,7 +297,7 @@ impl Mixer {
|
||||||
/// # use agb::sound::mixer::*;
|
/// # use agb::sound::mixer::*;
|
||||||
/// # use agb::*;
|
/// # use agb::*;
|
||||||
/// # fn foo(gba: &mut Gba) {
|
/// # fn foo(gba: &mut Gba) {
|
||||||
/// # let mut mixer = gba.mixer.mixer();
|
/// # let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz10512);
|
||||||
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
/// # const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav");
|
||||||
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
/// let mut channel = SoundChannel::new_high_priority(MY_BGM);
|
||||||
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
/// let bgm_channel_id = mixer.play_sound(channel).unwrap(); // will always be Some if high priority
|
||||||
|
@ -313,47 +317,32 @@ impl Mixer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// These work perfectly with swapping the buffers every vblank
|
fn set_asm_buffer_size(frequency: Frequency) {
|
||||||
// list here: http://deku.gbadev.org/program/sound1.html
|
|
||||||
#[cfg(all(not(feature = "freq18157"), not(feature = "freq32768")))]
|
|
||||||
mod constants {
|
|
||||||
pub const SOUND_FREQUENCY: i32 = 10512;
|
|
||||||
pub const SOUND_BUFFER_SIZE: usize = 176;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "freq18157")]
|
|
||||||
mod constants {
|
|
||||||
pub const SOUND_FREQUENCY: i32 = 18157;
|
|
||||||
pub const SOUND_BUFFER_SIZE: usize = 304;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "freq32768")]
|
|
||||||
mod constants {
|
|
||||||
pub const SOUND_FREQUENCY: i32 = 32768;
|
|
||||||
pub const SOUND_BUFFER_SIZE: usize = 560;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_asm_buffer_size() {
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
static mut agb_rs__buffer_size: usize;
|
static mut agb_rs__buffer_size: usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__buffer_size = constants::SOUND_BUFFER_SIZE;
|
agb_rs__buffer_size = frequency.buffer_size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C, align(4))]
|
struct SoundBuffer(Box<[i8], InternalAllocator>);
|
||||||
struct SoundBuffer([i8; constants::SOUND_BUFFER_SIZE * 2]);
|
|
||||||
|
|
||||||
impl Default for SoundBuffer {
|
impl SoundBuffer {
|
||||||
fn default() -> Self {
|
fn new(frequency: Frequency) -> Self {
|
||||||
Self([0; constants::SOUND_BUFFER_SIZE * 2])
|
let my_size = frequency.buffer_size() * 2;
|
||||||
|
let mut v = Vec::with_capacity_in(my_size, InternalAllocator);
|
||||||
|
v.resize(my_size, 0);
|
||||||
|
|
||||||
|
SoundBuffer(v.into_boxed_slice())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MixerBuffer {
|
struct MixerBuffer {
|
||||||
buffers: [SoundBuffer; 3],
|
buffers: [SoundBuffer; 3],
|
||||||
|
working_buffer: Box<[Num<i16, 4>], InternalAllocator>,
|
||||||
|
frequency: Frequency,
|
||||||
|
|
||||||
state: Mutex<RefCell<MixerBufferState>>,
|
state: Mutex<RefCell<MixerBufferState>>,
|
||||||
}
|
}
|
||||||
|
@ -391,16 +380,26 @@ impl MixerBufferState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MixerBuffer {
|
impl MixerBuffer {
|
||||||
fn new() -> Self {
|
fn new(frequency: Frequency) -> Self {
|
||||||
set_asm_buffer_size();
|
let mut working_buffer =
|
||||||
|
Vec::with_capacity_in(frequency.buffer_size() * 2, InternalAllocator);
|
||||||
|
working_buffer.resize(frequency.buffer_size() * 2, 0.into());
|
||||||
|
|
||||||
MixerBuffer {
|
MixerBuffer {
|
||||||
buffers: Default::default(),
|
buffers: [
|
||||||
|
SoundBuffer::new(frequency),
|
||||||
|
SoundBuffer::new(frequency),
|
||||||
|
SoundBuffer::new(frequency),
|
||||||
|
],
|
||||||
|
|
||||||
|
working_buffer: working_buffer.into_boxed_slice(),
|
||||||
|
|
||||||
state: Mutex::new(RefCell::new(MixerBufferState {
|
state: Mutex::new(RefCell::new(MixerBufferState {
|
||||||
active_buffer: 0,
|
active_buffer: 0,
|
||||||
playing_buffer: 0,
|
playing_buffer: 0,
|
||||||
})),
|
})),
|
||||||
|
|
||||||
|
frequency,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,15 +412,16 @@ impl MixerBuffer {
|
||||||
|
|
||||||
let (left_buffer, right_buffer) = self.buffers[buffer]
|
let (left_buffer, right_buffer) = self.buffers[buffer]
|
||||||
.0
|
.0
|
||||||
.split_at(constants::SOUND_BUFFER_SIZE);
|
.split_at(self.frequency.buffer_size());
|
||||||
|
|
||||||
hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left);
|
hw::enable_dma_for_sound(left_buffer, LeftOrRight::Left);
|
||||||
hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right);
|
hw::enable_dma_for_sound(right_buffer, LeftOrRight::Right);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_channels<'a>(&mut self, channels: impl Iterator<Item = &'a mut SoundChannel>) {
|
fn write_channels<'a>(&mut self, channels: impl Iterator<Item = &'a mut SoundChannel>) {
|
||||||
let mut buffer: [Num<i16, 4>; constants::SOUND_BUFFER_SIZE * 2] =
|
set_asm_buffer_size(self.frequency);
|
||||||
unsafe { transmute([0i16; constants::SOUND_BUFFER_SIZE * 2]) };
|
|
||||||
|
self.working_buffer.fill(0.into());
|
||||||
|
|
||||||
for channel in channels {
|
for channel in channels {
|
||||||
if channel.is_done {
|
if channel.is_done {
|
||||||
|
@ -434,7 +434,7 @@ impl MixerBuffer {
|
||||||
channel.playback_speed
|
channel.playback_speed
|
||||||
};
|
};
|
||||||
|
|
||||||
if (channel.pos + playback_speed * constants::SOUND_BUFFER_SIZE).floor()
|
if (channel.pos + playback_speed * self.frequency.buffer_size()).floor()
|
||||||
>= channel.data.len()
|
>= channel.data.len()
|
||||||
{
|
{
|
||||||
// TODO: This should probably play what's left rather than skip the last bit
|
// TODO: This should probably play what's left rather than skip the last bit
|
||||||
|
@ -450,7 +450,7 @@ impl MixerBuffer {
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__mixer_add_stereo(
|
agb_rs__mixer_add_stereo(
|
||||||
channel.data.as_ptr().add(channel.pos.floor()),
|
channel.data.as_ptr().add(channel.pos.floor()),
|
||||||
buffer.as_mut_ptr(),
|
self.working_buffer.as_mut_ptr(),
|
||||||
channel.volume,
|
channel.volume,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -461,7 +461,7 @@ impl MixerBuffer {
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__mixer_add(
|
agb_rs__mixer_add(
|
||||||
channel.data.as_ptr().add(channel.pos.floor()),
|
channel.data.as_ptr().add(channel.pos.floor()),
|
||||||
buffer.as_mut_ptr(),
|
self.working_buffer.as_mut_ptr(),
|
||||||
playback_speed,
|
playback_speed,
|
||||||
left_amount,
|
left_amount,
|
||||||
right_amount,
|
right_amount,
|
||||||
|
@ -469,7 +469,7 @@ impl MixerBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.pos += playback_speed * constants::SOUND_BUFFER_SIZE;
|
channel.pos += playback_speed * self.frequency.buffer_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
let write_buffer_index = free(|cs| self.state.borrow(cs).borrow_mut().active_advanced());
|
let write_buffer_index = free(|cs| self.state.borrow(cs).borrow_mut().active_advanced());
|
||||||
|
@ -477,7 +477,7 @@ impl MixerBuffer {
|
||||||
let write_buffer = &mut self.buffers[write_buffer_index].0;
|
let write_buffer = &mut self.buffers[write_buffer_index].0;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), buffer.as_ptr());
|
agb_rs__mixer_collapse(write_buffer.as_mut_ptr(), self.working_buffer.as_ptr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
use agb_fixnum::Vector2D;
|
||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
// use crate::display::object::AffineMatrixAttributes;
|
use crate::display::object::AffineMatrixAttributes;
|
||||||
|
use crate::fixnum::Num;
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
|
||||||
const fn swi_map(thumb_id: u32) -> u32 {
|
const fn swi_map(thumb_id: u32) -> u32 {
|
||||||
if cfg!(target_feature="thumb-mode") {
|
if cfg!(target_feature = "thumb-mode") {
|
||||||
thumb_id
|
thumb_id
|
||||||
} else {
|
} else {
|
||||||
thumb_id << 16
|
thumb_id << 16
|
||||||
|
@ -136,55 +139,125 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn affine_matrix(
|
#[repr(C, packed(4))]
|
||||||
// x_scale: Num<i16, 8>,
|
pub struct BgAffineSetData {
|
||||||
// y_scale: Num<i16, 8>,
|
pub matrix: AffineMatrixAttributes,
|
||||||
// rotation: u8,
|
pub position: Vector2D<Num<i32, 8>>,
|
||||||
// ) -> AffineMatrixAttributes {
|
}
|
||||||
// let mut result = AffineMatrixAttributes {
|
impl Default for BgAffineSetData {
|
||||||
// p_a: 0,
|
fn default() -> Self {
|
||||||
// p_b: 0,
|
Self {
|
||||||
// p_c: 0,
|
matrix: AffineMatrixAttributes::default(),
|
||||||
// p_d: 0,
|
position: (0, 0).into(),
|
||||||
// };
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// #[allow(dead_code)]
|
/// `rotation` is in revolutions.
|
||||||
// #[repr(C, packed)]
|
#[must_use]
|
||||||
// struct Input {
|
pub fn bg_affine_matrix(
|
||||||
// x_scale: i16,
|
bg_center: Vector2D<Num<i32, 8>>,
|
||||||
// y_scale: i16,
|
display_center: Vector2D<i16>,
|
||||||
// rotation: u16,
|
scale: Vector2D<Num<i16, 8>>,
|
||||||
// }
|
rotation: Num<u16, 8>,
|
||||||
|
) -> BgAffineSetData {
|
||||||
|
#[repr(C, packed(4))]
|
||||||
|
struct Input {
|
||||||
|
bg_center: Vector2D<Num<i32, 8>>,
|
||||||
|
display_center: Vector2D<i16>,
|
||||||
|
scale: Vector2D<Num<i16, 8>>,
|
||||||
|
rotation: Num<u16, 8>,
|
||||||
|
}
|
||||||
|
|
||||||
// let input = Input {
|
let input = Input {
|
||||||
// y_scale: x_scale.to_raw(),
|
bg_center,
|
||||||
// x_scale: y_scale.to_raw(),
|
display_center,
|
||||||
// rotation: rotation as u16,
|
scale,
|
||||||
// };
|
rotation,
|
||||||
|
};
|
||||||
|
|
||||||
// unsafe {
|
let mut output = MaybeUninit::uninit();
|
||||||
// asm!("swi 0x0F",
|
|
||||||
// in("r0") &input as *const Input as usize,
|
|
||||||
// in("r1") &mut result as *mut AffineMatrixAttributes as usize,
|
|
||||||
// in("r2") 1,
|
|
||||||
// in("r3") 2,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
// result
|
unsafe {
|
||||||
// }
|
asm!(
|
||||||
|
"swi {SWI}",
|
||||||
|
SWI = const { swi_map(0x0E) },
|
||||||
|
in("r0") &input as *const Input,
|
||||||
|
in("r1") output.as_mut_ptr(),
|
||||||
|
in("r2") 1,
|
||||||
|
|
||||||
// #[cfg(test)]
|
clobber_abi("C")
|
||||||
// mod tests {
|
);
|
||||||
// use super::*;
|
}
|
||||||
|
|
||||||
// #[test_case]
|
unsafe { output.assume_init() }
|
||||||
// fn affine(_gba: &mut crate::Gba) {
|
}
|
||||||
// // expect identity matrix
|
|
||||||
// let one: Num<i16, 8> = 1.into();
|
|
||||||
|
|
||||||
// let aff = affine_matrix(one, one, 0);
|
/// `rotation` is in revolutions.
|
||||||
// assert_eq!(aff.p_a, one.to_raw());
|
#[must_use]
|
||||||
// assert_eq!(aff.p_d, one.to_raw());
|
pub fn obj_affine_matrix(
|
||||||
// }
|
scale: Vector2D<Num<i16, 8>>,
|
||||||
// }
|
rotation: Num<u8, 8>,
|
||||||
|
) -> AffineMatrixAttributes {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[repr(C, packed(4))]
|
||||||
|
struct Input {
|
||||||
|
scale: Vector2D<Num<i16, 8>>,
|
||||||
|
rotation: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = Input {
|
||||||
|
scale,
|
||||||
|
rotation: u16::from(rotation.to_raw()) << 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut output = MaybeUninit::uninit();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
asm!(
|
||||||
|
"swi {SWI}",
|
||||||
|
SWI = const { swi_map(0x0F) },
|
||||||
|
in("r0") &input as *const Input,
|
||||||
|
in("r1") output.as_mut_ptr(),
|
||||||
|
in("r2") 1,
|
||||||
|
in("r3") 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { output.assume_init() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn affine_obj(_gba: &mut crate::Gba) {
|
||||||
|
// expect identity matrix
|
||||||
|
let one: Num<i16, 8> = 1.into();
|
||||||
|
|
||||||
|
let aff = obj_affine_matrix((one, one).into(), Num::default());
|
||||||
|
let (p_a, p_d) = (aff.p_a, aff.p_d);
|
||||||
|
|
||||||
|
assert_eq!(p_a, one);
|
||||||
|
assert_eq!(p_d, one);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn affine_bg(_gba: &mut crate::Gba) {
|
||||||
|
// expect the identity matrix
|
||||||
|
let aff = bg_affine_matrix(
|
||||||
|
(0, 0).into(),
|
||||||
|
(0i16, 0i16).into(),
|
||||||
|
(1i16, 1i16).into(),
|
||||||
|
0.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let matrix = aff.matrix;
|
||||||
|
let (p_a, p_b, p_c, p_d) = (matrix.p_a, matrix.p_b, matrix.p_c, matrix.p_d);
|
||||||
|
assert_eq!(p_a, 1.into());
|
||||||
|
assert_eq!(p_b, 0.into());
|
||||||
|
assert_eq!(p_c, 0.into());
|
||||||
|
assert_eq!(p_d, 1.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
agb = { version = "0.11.1", path = "../../agb", features = ["freq32768"] }
|
agb = { version = "0.11.1", path = "../../agb" }
|
||||||
bare-metal = "1"
|
bare-metal = "1"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
transparent_colour = "121105"
|
||||||
|
|
||||||
|
[image.stars]
|
||||||
|
filename = "stars.png"
|
||||||
|
tile_size = "8x8"
|
||||||
|
|
||||||
|
[image.title]
|
||||||
|
filename = "title-screen.png"
|
||||||
|
tile_size = "8x8"
|
||||||
|
|
||||||
|
[image.help]
|
||||||
|
filename = "help-text.png"
|
||||||
|
tile_size = "8x8"
|
||||||
|
|
||||||
[image.descriptions1]
|
[image.descriptions1]
|
||||||
filename = "descriptions1.png"
|
filename = "descriptions1.png"
|
||||||
tile_size = "8x8"
|
tile_size = "8x8"
|
||||||
transparent_colour = "121105"
|
|
||||||
|
|
||||||
[image.descriptions2]
|
[image.descriptions2]
|
||||||
filename = "descriptions2.png"
|
filename = "descriptions2.png"
|
||||||
tile_size = "8x8"
|
tile_size = "8x8"
|
||||||
transparent_colour = "121105"
|
|
|
@ -1,6 +0,0 @@
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
[image.help]
|
|
||||||
filename = "help-text.png"
|
|
||||||
tile_size = "8x8"
|
|
||||||
transparent_colour = "121105"
|
|
|
@ -1,10 +0,0 @@
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
[image.stars]
|
|
||||||
filename = "stars.png"
|
|
||||||
tile_size = "8x8"
|
|
||||||
transparent_colour = "121105"
|
|
||||||
|
|
||||||
[image.title]
|
|
||||||
filename = "title-screen.png"
|
|
||||||
tile_size = "8x8"
|
|
|
@ -1,21 +1,14 @@
|
||||||
use agb::{
|
use agb::{
|
||||||
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager},
|
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager},
|
||||||
include_gfx, rng,
|
include_gfx, rng,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::sfx::Sfx;
|
use crate::sfx::Sfx;
|
||||||
|
|
||||||
include_gfx!("gfx/stars.toml");
|
include_gfx!("gfx/backgrounds.toml");
|
||||||
|
|
||||||
include_gfx!("gfx/help.toml");
|
|
||||||
|
|
||||||
pub fn load_palettes(vram: &mut VRamManager) {
|
pub fn load_palettes(vram: &mut VRamManager) {
|
||||||
vram.set_background_palettes(&[
|
vram.set_background_palettes(backgrounds::PALETTES);
|
||||||
stars::stars.palettes[0].clone(),
|
|
||||||
crate::customise::DESCRIPTIONS_1_PALETTE.clone(),
|
|
||||||
crate::customise::DESCRIPTIONS_2_PALETTE.clone(),
|
|
||||||
help::help.palettes[0].clone(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_help_text(
|
pub(crate) fn load_help_text(
|
||||||
|
@ -24,42 +17,95 @@ pub(crate) fn load_help_text(
|
||||||
help_text_line: u16,
|
help_text_line: u16,
|
||||||
at_tile: (u16, u16),
|
at_tile: (u16, u16),
|
||||||
) {
|
) {
|
||||||
let help_tileset = TileSet::new(help::help.tiles, agb::display::tiled::TileFormat::FourBpp);
|
let help_tileset = TileSet::new(
|
||||||
|
backgrounds::help.tiles,
|
||||||
|
agb::display::tiled::TileFormat::FourBpp,
|
||||||
|
);
|
||||||
|
|
||||||
for x in 0..16 {
|
for x in 0..16 {
|
||||||
|
let tile_id = help_text_line * 16 + x;
|
||||||
|
|
||||||
background.set_tile(
|
background.set_tile(
|
||||||
vram,
|
vram,
|
||||||
(x + at_tile.0, at_tile.1).into(),
|
(x + at_tile.0, at_tile.1).into(),
|
||||||
&help_tileset,
|
&help_tileset,
|
||||||
TileSetting::new(help_text_line * 16 + x, false, false, 3),
|
TileSetting::new(
|
||||||
|
tile_id,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
backgrounds::help.palette_assignments[tile_id as usize],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_description(
|
||||||
|
face_id: usize,
|
||||||
|
descriptions_map: &mut RegularMap,
|
||||||
|
vram: &mut VRamManager,
|
||||||
|
) {
|
||||||
|
let (tileset, palette_assignments) = if face_id < 10 {
|
||||||
|
(
|
||||||
|
TileSet::new(
|
||||||
|
backgrounds::descriptions1.tiles,
|
||||||
|
agb::display::tiled::TileFormat::FourBpp,
|
||||||
|
),
|
||||||
|
backgrounds::descriptions1.palette_assignments,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
TileSet::new(
|
||||||
|
backgrounds::descriptions2.tiles,
|
||||||
|
agb::display::tiled::TileFormat::FourBpp,
|
||||||
|
),
|
||||||
|
backgrounds::descriptions2.palette_assignments,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
for y in 0..11 {
|
||||||
|
for x in 0..8 {
|
||||||
|
let tile_id = y * 8 + x + 8 * 11 * (face_id as u16 - 10);
|
||||||
|
descriptions_map.set_tile(
|
||||||
|
vram,
|
||||||
|
(x, y).into(),
|
||||||
|
&tileset,
|
||||||
|
TileSetting::new(tile_id, false, false, palette_assignments[tile_id as usize]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Expects a 64x32 map
|
// Expects a 64x32 map
|
||||||
fn create_background_map(map: &mut RegularMap, vram: &mut VRamManager, stars_tileset: &TileSet) {
|
fn create_background_map(map: &mut RegularMap, vram: &mut VRamManager, stars_tileset: &TileSet) {
|
||||||
for x in 0..64u16 {
|
for x in 0..64u16 {
|
||||||
for y in 0..32u16 {
|
for y in 0..32u16 {
|
||||||
let blank = rng::gen().rem_euclid(32) < 30;
|
let blank = rng::gen().rem_euclid(32) < 30;
|
||||||
|
|
||||||
let tile_id = if blank {
|
let (tile_id, palette_id) = if blank {
|
||||||
(1 << 10) - 1
|
((1 << 10) - 1, 0)
|
||||||
} else {
|
} else {
|
||||||
rng::gen().rem_euclid(64) as u16
|
let tile_id = rng::gen().rem_euclid(64) as u16;
|
||||||
|
(
|
||||||
|
tile_id,
|
||||||
|
backgrounds::stars.palette_assignments[tile_id as usize],
|
||||||
|
)
|
||||||
};
|
};
|
||||||
let tile_setting = TileSetting::new(tile_id, false, false, 0);
|
let tile_setting = TileSetting::new(tile_id, false, false, palette_id);
|
||||||
|
|
||||||
map.set_tile(vram, (x, y).into(), stars_tileset, tile_setting);
|
map.set_tile(vram, (x, y).into(), stars_tileset, tile_setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
map.set_scroll_pos((0u16, rng::gen().rem_euclid(8) as u16).into());
|
map.set_scroll_pos((0i16, rng::gen().rem_euclid(8) as i16).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
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((0_u16, 0_u16).into());
|
background.set_scroll_pos((0i16, 0).into());
|
||||||
vram.set_background_palettes(stars::title.palettes);
|
vram.set_background_palettes(backgrounds::PALETTES);
|
||||||
let tile_set = TileSet::new(stars::title.tiles, agb::display::tiled::TileFormat::FourBpp);
|
let tile_set = TileSet::new(
|
||||||
|
backgrounds::title.tiles,
|
||||||
|
agb::display::tiled::TileFormat::FourBpp,
|
||||||
|
);
|
||||||
background.hide();
|
background.hide();
|
||||||
|
|
||||||
for x in 0..30u16 {
|
for x in 0..30u16 {
|
||||||
|
@ -73,7 +119,7 @@ pub fn show_title_screen(background: &mut RegularMap, vram: &mut VRamManager, sf
|
||||||
tile_id,
|
tile_id,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
stars::title.palette_assignments[tile_id as usize],
|
backgrounds::title.palette_assignments[tile_id as usize],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -100,7 +146,7 @@ impl<'a> StarBackground<'a> {
|
||||||
background2: &'a mut RegularMap,
|
background2: &'a mut RegularMap,
|
||||||
vram: &'_ mut VRamManager,
|
vram: &'_ mut VRamManager,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let stars_tileset = TileSet::new(stars::stars.tiles, TileFormat::FourBpp);
|
let stars_tileset = TileSet::new(backgrounds::stars.tiles, TileFormat::FourBpp);
|
||||||
create_background_map(background1, vram, &stars_tileset);
|
create_background_map(background1, vram, &stars_tileset);
|
||||||
create_background_map(background2, vram, &stars_tileset);
|
create_background_map(background2, vram, &stars_tileset);
|
||||||
|
|
||||||
|
@ -116,13 +162,13 @@ impl<'a> StarBackground<'a> {
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
if self.background1_timer == 0 {
|
if self.background1_timer == 0 {
|
||||||
self.background1
|
self.background1
|
||||||
.set_scroll_pos(self.background1.scroll_pos() + (1u16, 0).into());
|
.set_scroll_pos(self.background1.scroll_pos() + (1i16, 0).into());
|
||||||
self.background1_timer = 2;
|
self.background1_timer = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.background2_timer == 0 {
|
if self.background2_timer == 0 {
|
||||||
self.background2
|
self.background2
|
||||||
.set_scroll_pos(self.background2.scroll_pos() + (1u16, 0).into());
|
.set_scroll_pos(self.background2.scroll_pos() + (1i16, 0).into());
|
||||||
self.background2_timer = 3;
|
self.background2_timer = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::sfx::Sfx;
|
||||||
use crate::{
|
use crate::{
|
||||||
graphics::SELECT_BOX, level_generation::generate_attack, Agb, EnemyAttackType, Face, PlayerDice,
|
graphics::SELECT_BOX, level_generation::generate_attack, Agb, EnemyAttackType, Face, PlayerDice,
|
||||||
};
|
};
|
||||||
use agb::display::tiled::RegularMap;
|
use agb::display::tiled::{RegularMap, TiledMap};
|
||||||
use agb::{hash_map::HashMap, input::Button};
|
use agb::{hash_map::HashMap, input::Button};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
@ -207,7 +207,7 @@ impl RolledDice {
|
||||||
let heal = *face_counts.entry(Face::Heal).or_default();
|
let heal = *face_counts.entry(Face::Heal).or_default();
|
||||||
if heal != 0 {
|
if heal != 0 {
|
||||||
actions.push(Action::PlayerHeal {
|
actions.push(Action::PlayerHeal {
|
||||||
amount: ((heal * (heal + 1)) / 2) as u32,
|
amount: (heal * (heal + 1)) / 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,7 +488,7 @@ pub(crate) fn battle_screen(
|
||||||
agb.sfx.battle();
|
agb.sfx.battle();
|
||||||
agb.sfx.frame();
|
agb.sfx.frame();
|
||||||
|
|
||||||
help_background.set_scroll_pos((u16::MAX - 16, u16::MAX - 97).into());
|
help_background.set_scroll_pos((-16i16, -97i16).into());
|
||||||
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,26 +1,20 @@
|
||||||
use agb::{
|
use agb::{
|
||||||
display::{
|
display::{
|
||||||
object::{Object, ObjectController},
|
object::{Object, ObjectController},
|
||||||
palette16::Palette16,
|
tiled::{RegularMap, TiledMap},
|
||||||
tiled::{RegularMap, TileSet, TileSetting},
|
|
||||||
HEIGHT, WIDTH,
|
HEIGHT, WIDTH,
|
||||||
},
|
},
|
||||||
include_gfx,
|
|
||||||
input::{Button, Tri},
|
input::{Button, Tri},
|
||||||
};
|
};
|
||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
background::load_description,
|
||||||
graphics::{FACE_SPRITES, MODIFIED_BOX, SELECTED_BOX, SELECT_BOX},
|
graphics::{FACE_SPRITES, MODIFIED_BOX, SELECTED_BOX, SELECT_BOX},
|
||||||
Agb, Die, Face, PlayerDice,
|
Agb, Die, Face, PlayerDice,
|
||||||
};
|
};
|
||||||
|
|
||||||
include_gfx!("gfx/descriptions.toml");
|
|
||||||
|
|
||||||
pub const DESCRIPTIONS_1_PALETTE: &Palette16 = &descriptions::descriptions1.palettes[0];
|
|
||||||
pub const DESCRIPTIONS_2_PALETTE: &Palette16 = &descriptions::descriptions2.palettes[0];
|
|
||||||
|
|
||||||
enum CustomiseState {
|
enum CustomiseState {
|
||||||
Dice,
|
Dice,
|
||||||
Face,
|
Face,
|
||||||
|
@ -169,20 +163,11 @@ pub(crate) fn customise_screen(
|
||||||
) -> PlayerDice {
|
) -> PlayerDice {
|
||||||
agb.sfx.customise();
|
agb.sfx.customise();
|
||||||
agb.sfx.frame();
|
agb.sfx.frame();
|
||||||
descriptions_map.set_scroll_pos((u16::MAX - 174, u16::MAX - 52).into());
|
descriptions_map.set_scroll_pos((-174i16, -52).into());
|
||||||
|
|
||||||
help_background.set_scroll_pos((u16::MAX - 148, u16::MAX - 34).into());
|
help_background.set_scroll_pos((-148i16, -34).into());
|
||||||
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));
|
||||||
|
|
||||||
let descriptions_1_tileset = TileSet::new(
|
|
||||||
descriptions::descriptions1.tiles,
|
|
||||||
agb::display::tiled::TileFormat::FourBpp,
|
|
||||||
);
|
|
||||||
let descriptions_2_tileset = TileSet::new(
|
|
||||||
descriptions::descriptions2.tiles,
|
|
||||||
agb::display::tiled::TileFormat::FourBpp,
|
|
||||||
);
|
|
||||||
|
|
||||||
// create the dice
|
// create the dice
|
||||||
|
|
||||||
let mut _net = create_net(&agb.obj, &player_dice.dice[0], &[]);
|
let mut _net = create_net(&agb.obj, &player_dice.dice[0], &[]);
|
||||||
|
@ -301,37 +286,11 @@ pub(crate) fn customise_screen(
|
||||||
|
|
||||||
if (upgrades[cursor.upgrade] as u32) < 17 {
|
if (upgrades[cursor.upgrade] as u32) < 17 {
|
||||||
if cursor.upgrade != old_updade {
|
if cursor.upgrade != old_updade {
|
||||||
for y in 0..11 {
|
load_description(
|
||||||
for x in 0..8 {
|
upgrades[cursor.upgrade] as usize,
|
||||||
if (upgrades[cursor.upgrade] as usize) < 10 {
|
descriptions_map,
|
||||||
descriptions_map.set_tile(
|
&mut agb.vram,
|
||||||
&mut agb.vram,
|
);
|
||||||
(x, y).into(),
|
|
||||||
&descriptions_1_tileset,
|
|
||||||
TileSetting::new(
|
|
||||||
y * 8 + x + 8 * 11 * upgrades[cursor.upgrade] as u16,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
descriptions_map.set_tile(
|
|
||||||
&mut agb.vram,
|
|
||||||
(x, y).into(),
|
|
||||||
&descriptions_2_tileset,
|
|
||||||
TileSetting::new(
|
|
||||||
y * 8
|
|
||||||
+ x
|
|
||||||
+ 8 * 11 * (upgrades[cursor.upgrade] as u16 - 10),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
descriptions_map.show();
|
descriptions_map.show();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,11 +10,11 @@
|
||||||
// which won't be a particularly clear error message.
|
// which won't be a particularly clear error message.
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use agb::display;
|
|
||||||
use agb::display::object::ObjectController;
|
use agb::display::object::ObjectController;
|
||||||
use agb::display::tiled::VRamManager;
|
use agb::display::tiled::{TiledMap, VRamManager};
|
||||||
use agb::display::Priority;
|
use agb::display::Priority;
|
||||||
use agb::interrupt::VBlank;
|
use agb::interrupt::VBlank;
|
||||||
|
use agb::{display, sound::mixer::Frequency};
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
|
@ -138,7 +138,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
let mut star_background = StarBackground::new(&mut background0, &mut background1, &mut vram);
|
let mut star_background = StarBackground::new(&mut background0, &mut background1, &mut vram);
|
||||||
star_background.commit(&mut vram);
|
star_background.commit(&mut vram);
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz32768);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
let _interrupt_handler = mixer.setup_interrupt_handler();
|
let _interrupt_handler = mixer.setup_interrupt_handler();
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|
||||||
|
transparent_colour = "2ce8f4"
|
||||||
|
|
||||||
[image.thanks_for_playing]
|
[image.thanks_for_playing]
|
||||||
filename = "thanks_for_playing.png"
|
filename = "thanks_for_playing.png"
|
||||||
tile_size = "8x8"
|
tile_size = "8x8"
|
||||||
transparent_colour = "2ce8f4"
|
|
||||||
|
|
||||||
[image.splash]
|
[image.splash]
|
||||||
filename = "splash.png"
|
filename = "splash.png"
|
||||||
tile_size = "8x8"
|
tile_size = "8x8"
|
||||||
transparent_colour = "2ce8f4"
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|
||||||
|
transparent_colour = "2ce8f4"
|
||||||
|
|
||||||
[image.background]
|
[image.background]
|
||||||
filename = "tile_sheet.png"
|
filename = "tile_sheet.png"
|
||||||
tile_size = "8x8"
|
tile_size = "8x8"
|
||||||
transparent_colour = "2ce8f4"
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use agb::display::{
|
use agb::display::{
|
||||||
tiled::{RegularMap, TileSet, TileSetting, VRamManager},
|
tiled::{RegularMap, TileSet, TileSetting, TiledMap, VRamManager},
|
||||||
HEIGHT, WIDTH,
|
HEIGHT, WIDTH,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,5 +35,5 @@ pub fn write_level(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.set_scroll_pos((-(WIDTH / 2 - 7 * 8 / 2) as u16, -(HEIGHT / 2 - 4) as u16).into());
|
map.set_scroll_pos((-(WIDTH / 2 - 7 * 8 / 2) as i16, -(HEIGHT / 2 - 4) as i16).into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,13 @@ use agb::{
|
||||||
object::{Graphics, Object, ObjectController, Tag, TagMap},
|
object::{Graphics, Object, ObjectController, Tag, TagMap},
|
||||||
tiled::{
|
tiled::{
|
||||||
InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet,
|
InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet,
|
||||||
TileSetting, VRamManager,
|
TileSetting, TiledMap, VRamManager,
|
||||||
},
|
},
|
||||||
Priority, HEIGHT, WIDTH,
|
Priority, HEIGHT, WIDTH,
|
||||||
},
|
},
|
||||||
fixnum::{FixedNum, Vector2D},
|
fixnum::{FixedNum, Vector2D},
|
||||||
input::{self, Button, ButtonController},
|
input::{self, Button, ButtonController},
|
||||||
|
sound::mixer::Frequency,
|
||||||
};
|
};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
|
@ -785,7 +786,7 @@ fn agb_main(mut gba: agb::Gba) -> ! {
|
||||||
|
|
||||||
pub fn main(mut agb: agb::Gba) -> ! {
|
pub fn main(mut agb: agb::Gba) -> ! {
|
||||||
let (tiled, mut vram) = agb.display.video.tiled0();
|
let (tiled, mut vram) = agb.display.video.tiled0();
|
||||||
vram.set_background_palettes(tile_sheet::background.palettes);
|
vram.set_background_palettes(tile_sheet::PALETTES);
|
||||||
let mut splash_screen = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
let mut splash_screen = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
||||||
let mut world_display = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
let mut world_display = tiled.background(Priority::P0, RegularBackgroundSize::Background32x32);
|
||||||
|
|
||||||
|
@ -817,10 +818,10 @@ pub fn main(mut agb: agb::Gba) -> ! {
|
||||||
world_display.commit(&mut vram);
|
world_display.commit(&mut vram);
|
||||||
world_display.show();
|
world_display.show();
|
||||||
|
|
||||||
vram.set_background_palettes(tile_sheet::background.palettes);
|
vram.set_background_palettes(tile_sheet::PALETTES);
|
||||||
|
|
||||||
let object = agb.display.object.get();
|
let object = agb.display.object.get();
|
||||||
let mut mixer = agb.mixer.mixer();
|
let mut mixer = agb.mixer.mixer(Frequency::Hz10512);
|
||||||
|
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
let mut music_box = sfx::MusicBox::new();
|
let mut music_box = sfx::MusicBox::new();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::sfx::MusicBox;
|
use super::sfx::MusicBox;
|
||||||
use agb::{
|
use agb::{
|
||||||
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager},
|
display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager},
|
||||||
sound::mixer::Mixer,
|
sound::mixer::Mixer,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,21 +18,14 @@ pub fn show_splash_screen(
|
||||||
map: &mut RegularMap,
|
map: &mut RegularMap,
|
||||||
vram: &mut VRamManager,
|
vram: &mut VRamManager,
|
||||||
) {
|
) {
|
||||||
map.set_scroll_pos((0u16, 0u16).into());
|
map.set_scroll_pos((0i16, 0i16).into());
|
||||||
let (tileset, palette) = match which {
|
let tileset = match which {
|
||||||
SplashScreen::Start => {
|
SplashScreen::Start => TileSet::new(splash_screens::splash.tiles, TileFormat::FourBpp),
|
||||||
let tileset = TileSet::new(splash_screens::splash.tiles, TileFormat::FourBpp);
|
|
||||||
|
|
||||||
(tileset, splash_screens::splash.palettes)
|
SplashScreen::End => TileSet::new(
|
||||||
}
|
splash_screens::thanks_for_playing.tiles,
|
||||||
SplashScreen::End => {
|
TileFormat::FourBpp,
|
||||||
let tileset = TileSet::new(
|
),
|
||||||
splash_screens::thanks_for_playing.tiles,
|
|
||||||
TileFormat::FourBpp,
|
|
||||||
);
|
|
||||||
|
|
||||||
(tileset, splash_screens::thanks_for_playing.palettes)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let vblank = agb::interrupt::VBlank::get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
|
@ -77,7 +70,7 @@ pub fn show_splash_screen(
|
||||||
}
|
}
|
||||||
|
|
||||||
map.commit(vram);
|
map.commit(vram);
|
||||||
vram.set_background_palettes(palette);
|
vram.set_background_palettes(splash_screens::PALETTES);
|
||||||
map.show();
|
map.show();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -7,7 +7,7 @@ edition = "2018"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
agb = { path = "../../agb", version = "0.11.1", features = ["freq18157"]}
|
agb = { path = "../../agb", version = "0.11.1" }
|
||||||
generational-arena = { version = "0.2", default-features = false }
|
generational-arena = { version = "0.2", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|
||||||
|
transparent_colour = "53269a"
|
||||||
|
|
||||||
[image.background]
|
[image.background]
|
||||||
filename = "background.png"
|
filename = "background.png"
|
||||||
tile_size = "8x8"
|
tile_size = "8x8"
|
||||||
transparent_colour = "53269a"
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
[image.objects]
|
|
||||||
filename = "objects.png"
|
|
||||||
tile_size = "16x16"
|
|
||||||
transparent_colour = "53269a"
|
|
||||||
|
|
||||||
[image.boss]
|
|
||||||
filename = "boss.png"
|
|
||||||
tile_size = "32x32"
|
|
||||||
transparent_colour = "53269a"
|
|
|
@ -22,6 +22,7 @@ use agb::{
|
||||||
input::{Button, ButtonController, Tri},
|
input::{Button, ButtonController, Tri},
|
||||||
interrupt::VBlank,
|
interrupt::VBlank,
|
||||||
rng,
|
rng,
|
||||||
|
sound::mixer::Frequency,
|
||||||
};
|
};
|
||||||
use generational_arena::Arena;
|
use generational_arena::Arena;
|
||||||
use sfx::Sfx;
|
use sfx::Sfx;
|
||||||
|
@ -129,7 +130,7 @@ impl<'a> Level<'a> {
|
||||||
let factor: Number = Number::new(1) / Number::new(8);
|
let factor: Number = Number::new(1) / Number::new(8);
|
||||||
let (x, y) = (v * factor).floor().get();
|
let (x, y) = (v * factor).floor().get();
|
||||||
|
|
||||||
if (x < 0 || x > tilemap::WIDTH as i32) || (y < 0 || y > tilemap::HEIGHT as i32) {
|
if !(0..=tilemap::WIDTH).contains(&x) || !(0..=tilemap::HEIGHT).contains(&y) {
|
||||||
return Some(Rect::new((x * 8, y * 8).into(), (8, 8).into()));
|
return Some(Rect::new((x * 8, y * 8).into(), (8, 8).into()));
|
||||||
}
|
}
|
||||||
let position = tilemap::WIDTH as usize * y as usize + x as usize;
|
let position = tilemap::WIDTH as usize * y as usize + x as usize;
|
||||||
|
@ -1878,7 +1879,7 @@ enum MoveState {
|
||||||
impl<'a> Game<'a> {
|
impl<'a> Game<'a> {
|
||||||
fn has_just_reached_end(&self) -> bool {
|
fn has_just_reached_end(&self) -> bool {
|
||||||
match self.boss {
|
match self.boss {
|
||||||
BossState::NotSpawned => self.offset.x.floor() + 248 >= tilemap::WIDTH as i32 * 8,
|
BossState::NotSpawned => self.offset.x.floor() + 248 >= tilemap::WIDTH * 8,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1901,13 +1902,13 @@ impl<'a> Game<'a> {
|
||||||
|
|
||||||
if self.has_just_reached_end() {
|
if self.has_just_reached_end() {
|
||||||
sfx.boss();
|
sfx.boss();
|
||||||
self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into();
|
self.offset.x = (tilemap::WIDTH * 8 - 248).into();
|
||||||
self.move_state = MoveState::PinnedAtEnd;
|
self.move_state = MoveState::PinnedAtEnd;
|
||||||
self.boss = BossState::Active(Boss::new(object_controller, self.offset))
|
self.boss = BossState::Active(Boss::new(object_controller, self.offset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MoveState::PinnedAtEnd => {
|
MoveState::PinnedAtEnd => {
|
||||||
self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into();
|
self.offset.x = (tilemap::WIDTH * 8 - 248).into();
|
||||||
}
|
}
|
||||||
MoveState::FollowingPlayer => {
|
MoveState::FollowingPlayer => {
|
||||||
Game::update_sunrise(vram, self.sunrise_timer);
|
Game::update_sunrise(vram, self.sunrise_timer);
|
||||||
|
@ -1917,8 +1918,8 @@ impl<'a> Game<'a> {
|
||||||
let difference = self.player.entity.position.x - (self.offset.x + WIDTH / 2);
|
let difference = self.player.entity.position.x - (self.offset.x + WIDTH / 2);
|
||||||
|
|
||||||
self.offset.x += difference / 8;
|
self.offset.x += difference / 8;
|
||||||
if self.offset.x > (tilemap::WIDTH as i32 * 8 - 248).into() {
|
if self.offset.x > (tilemap::WIDTH * 8 - 248).into() {
|
||||||
self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into();
|
self.offset.x = (tilemap::WIDTH * 8 - 248).into();
|
||||||
} else if self.offset.x < 8.into() {
|
} else if self.offset.x < 8.into() {
|
||||||
self.offset.x = 8.into();
|
self.offset.x = 8.into();
|
||||||
self.move_state = MoveState::Ending;
|
self.move_state = MoveState::Ending;
|
||||||
|
@ -2146,7 +2147,7 @@ impl<'a> Game<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_sunrise(vram: &mut VRamManager, time: u16) {
|
fn update_sunrise(vram: &mut VRamManager, time: u16) {
|
||||||
let mut modified_palette = background::background.palettes[0].clone();
|
let mut modified_palette = background::PALETTES[0].clone();
|
||||||
|
|
||||||
let a = modified_palette.colour(0);
|
let a = modified_palette.colour(0);
|
||||||
let b = modified_palette.colour(1);
|
let b = modified_palette.colour(1);
|
||||||
|
@ -2160,7 +2161,7 @@ impl<'a> Game<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_fade_out(vram: &mut VRamManager, time: u16) {
|
fn update_fade_out(vram: &mut VRamManager, time: u16) {
|
||||||
let mut modified_palette = background::background.palettes[0].clone();
|
let mut modified_palette = background::PALETTES[0].clone();
|
||||||
|
|
||||||
let c = modified_palette.colour(2);
|
let c = modified_palette.colour(2);
|
||||||
|
|
||||||
|
@ -2205,7 +2206,7 @@ fn game_with_level(gba: &mut agb::Gba) {
|
||||||
let vblank = agb::interrupt::VBlank::get();
|
let vblank = agb::interrupt::VBlank::get();
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
|
|
||||||
let mut mixer = gba.mixer.mixer();
|
let mut mixer = gba.mixer.mixer(Frequency::Hz18157);
|
||||||
mixer.enable();
|
mixer.enable();
|
||||||
|
|
||||||
let mut sfx = sfx::Sfx::new(&mut mixer);
|
let mut sfx = sfx::Sfx::new(&mut mixer);
|
||||||
|
@ -2216,7 +2217,7 @@ fn game_with_level(gba: &mut agb::Gba) {
|
||||||
loop {
|
loop {
|
||||||
let (background, mut vram) = gba.display.video.tiled0();
|
let (background, mut vram) = gba.display.video.tiled0();
|
||||||
|
|
||||||
vram.set_background_palettes(background::background.palettes);
|
vram.set_background_palettes(background::PALETTES);
|
||||||
|
|
||||||
let tileset = TileSet::new(background::background.tiles, TileFormat::FourBpp);
|
let tileset = TileSet::new(background::background.tiles, TileFormat::FourBpp);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue