diff --git a/.vscode/agb.code-workspace b/.vscode/agb.code-workspace index 548a9811..f7b0fd40 100644 --- a/.vscode/agb.code-workspace +++ b/.vscode/agb.code-workspace @@ -40,4 +40,4 @@ "path": "../tools" } ] -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 4db58884..5809d563 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 support for save files. - 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 - 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. +- 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 the fast magnitude function in agb_fixnum. This is also used in fast_normalise. Previously only worked for positive (x, y). diff --git a/README.md b/README.md index df80be43..6da2f92a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ ## 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) This is a library for making games on the Game Boy Advance using the Rust diff --git a/agb-fixnum/src/lib.rs b/agb-fixnum/src/lib.rs index 1d3013d1..cc5669a6 100644 --- a/agb-fixnum/src/lib.rs +++ b/agb-fixnum/src/lib.rs @@ -570,7 +570,8 @@ impl Debug for Num { } /// 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 { /// The x coordinate pub x: T, @@ -1015,6 +1016,14 @@ impl Vector2D { } } +impl> Neg for Vector2D { + type Output = Self; + + fn neg(self) -> Self::Output { + (-self.x, -self.y).into() + } +} + #[cfg(test)] mod tests { @@ -1218,6 +1227,15 @@ mod tests { } } + #[test] + fn test_only_frac_bits() { + let quarter: Num = num!(0.25); + let neg_quarter: Num = num!(-0.25); + + assert_eq!(quarter + quarter, num!(0.5)); + assert_eq!(neg_quarter + neg_quarter, num!(-0.5)); + } + #[test] fn test_vector_multiplication_and_division() { let a: Vector2D = (1, 2).into(); diff --git a/agb-image-converter/src/colour.rs b/agb-image-converter/src/colour.rs index 917b3b99..b401b9bd 100644 --- a/agb-image-converter/src/colour.rs +++ b/agb-image-converter/src/colour.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Colour { pub r: u8, @@ -20,3 +22,22 @@ impl Colour { self.a != 255 } } + +impl FromStr for Colour { + type Err = String; + + fn from_str(colour: &str) -> Result { + 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)) + } +} diff --git a/agb-image-converter/src/config.rs b/agb-image-converter/src/config.rs index 5e0ce482..7379a6d5 100644 --- a/agb-image-converter/src/config.rs +++ b/agb-image-converter/src/config.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use std::collections::HashMap; use std::fs; -use crate::{Colour, TileSize}; +use crate::{Colour, Colours, TileSize}; pub(crate) fn parse(filename: &str) -> Box { let config_toml = @@ -23,18 +23,20 @@ pub(crate) fn parse(filename: &str) -> Box { pub(crate) trait Config { fn crate_prefix(&self) -> String; fn images(&self) -> HashMap; + fn transparent_colour(&self) -> Option; } pub(crate) trait Image { fn filename(&self) -> String; - fn transparent_colour(&self) -> Option; fn tilesize(&self) -> TileSize; + fn colours(&self) -> Colours; } #[derive(Deserialize)] pub struct ConfigV1 { version: String, crate_prefix: Option, + transparent_colour: Option, image: HashMap, } @@ -52,6 +54,21 @@ impl Config for ConfigV1 { .map(|(filename, image)| (filename.clone(), image as &dyn Image)) .collect() } + + fn transparent_colour(&self) -> Option { + 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)] @@ -59,6 +76,7 @@ pub struct ImageV1 { filename: String, transparent_colour: Option, tile_size: TileSizeV1, + colours: Option, } impl Image for ImageV1 { @@ -66,25 +84,25 @@ impl Image for ImageV1 { self.filename.clone() } - fn transparent_colour(&self) -> Option { - 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 { 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 { + self.transparent_colour + .as_ref() + .map(|colour| colour.parse().unwrap()) + } } #[derive(Deserialize, Clone, Copy)] diff --git a/agb-image-converter/src/lib.rs b/agb-image-converter/src/lib.rs index 68260644..39ae0a92 100644 --- a/agb-image-converter/src/lib.rs +++ b/agb-image-converter/src/lib.rs @@ -1,10 +1,12 @@ -use palette16::Palette16OptimisationResults; +use palette16::{Palette16OptimisationResults, Palette16Optimiser}; +use palette256::Palette256; use proc_macro::TokenStream; use proc_macro2::Literal; use syn::parse::Parser; use syn::{parse_macro_input, punctuated::Punctuated, LitStr}; use syn::{Expr, ExprLit, Lit}; +use std::collections::HashMap; use std::path::PathBuf; use std::{iter, path::Path, str}; @@ -16,6 +18,7 @@ mod config; mod font_loader; mod image_loader; mod palette16; +mod palette256; mod rust_generator; use image::GenericImageView; @@ -30,6 +33,11 @@ pub(crate) enum TileSize { Tile32, } +pub(crate) enum Colours { + Colours16, + Colours256, +} + impl TileSize { fn to_size(self) -> usize { match self { @@ -63,14 +71,71 @@ pub fn include_gfx(input: TokenStream) -> TokenStream { let include_path = path.to_string_lossy(); 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! { mod #module_name { const _: &[u8] = include_bytes!(#include_path); + #palette_code + #(#image_code)* } }; @@ -132,7 +197,7 @@ pub fn include_aseprite_inner(input: TokenStream) -> TokenStream { 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| { quote! { @@ -214,38 +279,23 @@ fn convert_image( parent: &Path, variable_name: &str, crate_prefix: &str, + optimisation_results: &Palette16OptimisationResults, + assignment_offset: Option, ) -> proc_macro2::TokenStream { let image_filename = &parent.join(&settings.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( variable_name, - &optimisation_results, + optimisation_results, &image, &image_filename.to_string_lossy(), settings.tilesize(), crate_prefix.to_owned(), + assignment_offset, ) } -fn optimiser_for_image( - image: &Image, - tile_size: usize, - transparent_colour: Option, -) -> 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( palette_optimiser: &mut palette16::Palette16Optimiser, image: &Image, @@ -275,7 +325,7 @@ fn add_to_optimiser( } } -fn palete_tile_data( +fn palette_tile_data( optimiser: &Palette16OptimisationResults, images: &[Image], ) -> (Vec>, Vec, Vec) { @@ -295,41 +345,87 @@ fn palete_tile_data( .collect(); let mut tile_data = Vec::new(); + let tile_size = TileSize::Tile8; for image in images { - let tile_size = 8; - let tiles_x = image.width / tile_size; - let tiles_y = image.height / tile_size; + add_image_to_tile_data(&mut tile_data, image, tile_size, optimiser, 0) + } - for y in 0..tiles_y { - for x in 0..tiles_x { - let palette_index = optimiser.assignments[y * tiles_x + x]; - let palette = &optimiser.optimised_palettes[palette_index]; + let tile_data = collapse_to_4bpp(&tile_data); - 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 assignments = optimiser.assignments.clone(); + + (palette_data, tile_data, assignments) +} + +fn collapse_to_4bpp(tile_data: &[u8]) -> Vec { + tile_data + .chunks(2) + .map(|chunk| chunk[0] | (chunk[1] << 4)) + .collect() +} + +fn add_image_to_tile_data( + tile_data: &mut Vec, + 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 - .chunks(2) - .map(|chunk| chunk[0] | (chunk[1] << 4)) +fn add_image_256_to_tile_data( + tile_data: &mut Vec, + 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(); - let assignments = optimiser.assignments.clone(); - - (palette_data, tile_data, assignments) + for y in 0..tiles_y { + for x in 0..tiles_x { + 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 { diff --git a/agb-image-converter/src/palette16.rs b/agb-image-converter/src/palette16.rs index 01d0af0f..2878bad6 100644 --- a/agb-image-converter/src/palette16.rs +++ b/agb-image-converter/src/palette16.rs @@ -28,6 +28,19 @@ impl Palette16 { 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) -> u8 { let colour_to_search = match (transparent_colour, colour.is_transparent()) { (Some(transparent_colour), true) => transparent_colour, @@ -45,6 +58,10 @@ impl Palette16 { }) as u8 } + pub fn colours(&self) -> impl Iterator { + self.colours.iter() + } + fn union_length(&self, other: &Palette16) -> usize { self.colours .iter() @@ -150,9 +167,10 @@ impl Palette16Optimiser { fn find_maximal_palette_for(&self, unsatisfied_palettes: &HashSet) -> Palette16 { let mut palette = Palette16::new(); - if let Some(transparent_colour) = self.transparent_colour { - palette.add_colour(transparent_colour); - } + palette.add_colour( + self.transparent_colour + .unwrap_or_else(|| Colour::from_rgb(255, 0, 255, 0)), + ); loop { let mut colour_usage = vec![0; MAX_COLOURS]; diff --git a/agb-image-converter/src/palette256.rs b/agb-image-converter/src/palette256.rs new file mode 100644 index 00000000..fc9b47ff --- /dev/null +++ b/agb-image-converter/src/palette256.rs @@ -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, +} + +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, + } + } +} diff --git a/agb-image-converter/src/rust_generator.rs b/agb-image-converter/src/rust_generator.rs index f289ffeb..e15cae6d 100644 --- a/agb-image-converter/src/rust_generator.rs +++ b/agb-image-converter/src/rust_generator.rs @@ -1,5 +1,5 @@ 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 proc_macro2::TokenStream; @@ -7,18 +7,13 @@ use quote::{format_ident, quote}; use std::iter; -pub(crate) fn generate_code( - output_variable_name: &str, +pub(crate) fn generate_palette_code( results: &Palette16OptimisationResults, - image: &Image, - image_filename: &str, - tile_size: TileSize, - crate_prefix: String, + crate_prefix: &str, ) -> TokenStream { 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 .clone() .into_iter() @@ -34,57 +29,63 @@ pub(crate) fn generate_code( } }); - let tile_size = tile_size.to_size(); - - 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)); - } - } - } - } - } + quote! { + pub const PALETTES: &[#crate_prefix::display::palette16::Palette16] = &[#(#palettes),*]; } +} - let tile_data: Vec<_> = tile_data - .chunks(2) - .map(|chunk| (chunk[1] << 4) | chunk[0]) - .collect(); +pub(crate) fn generate_code( + output_variable_name: &str, + results: &Palette16OptimisationResults, + image: &Image, + image_filename: &str, + tile_size: TileSize, + crate_prefix: String, + assignment_offset: Option, +) -> 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 assignments = results.assignments.iter().map(|&x| x as u8); - quote! { #[allow(non_upper_case_globals)] pub const #output_variable_name: #crate_prefix::display::tile_data::TileData = { const _: &[u8] = include_bytes!(#image_filename); - const PALETTE_DATA: &[#crate_prefix::display::palette16::Palette16] = &[ - #(#palette_data),* - ]; - const TILE_DATA: &[u8] = #data; const PALETTE_ASSIGNMENT: &[u8] = &[ #(#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) }; } } diff --git a/agb-sound-converter/Cargo.toml b/agb-sound-converter/Cargo.toml index 5d6f316d..caea2f60 100644 --- a/agb-sound-converter/Cargo.toml +++ b/agb-sound-converter/Cargo.toml @@ -18,10 +18,6 @@ debug = true [lib] proc-macro = true -[features] -freq18157 = [] -freq32768 = [] - [dependencies] hound = "3.5" syn = "1" diff --git a/agb-sound-converter/src/lib.rs b/agb-sound-converter/src/lib.rs index b04f280b..5cbc2446 100644 --- a/agb-sound-converter/src/lib.rs +++ b/agb-sound-converter/src/lib.rs @@ -6,15 +6,6 @@ use quote::{quote, ToTokens}; use std::path::Path; 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; struct ByteString<'a>(&'a [u8]); impl ToTokens for ByteString<'_> { @@ -37,13 +28,6 @@ pub fn include_wav(input: TokenStream) -> TokenStream { let wav_reader = hound::WavReader::open(&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 = samples_from_reader(wav_reader).collect(); let samples = ByteString(&samples); diff --git a/agb/Cargo.toml b/agb/Cargo.toml index 83afc851..29070813 100644 --- a/agb/Cargo.toml +++ b/agb/Cargo.toml @@ -17,8 +17,6 @@ debug = true [features] default = ["testing"] -freq18157 = ["agb_sound_converter/freq18157"] -freq32768 = ["agb_sound_converter/freq32768"] testing = [] [dependencies] @@ -34,7 +32,3 @@ rustc-hash = { version = "1", default-features = false } [package.metadata.docs.rs] default-target = "thumbv6m-none-eabi" targets = [] - -[[example]] -name = "mixer_32768" -required-features = ["freq32768"] \ No newline at end of file diff --git a/agb/examples/affine_background.rs b/agb/examples/affine_background.rs new file mode 100644 index 00000000..7d783c25 --- /dev/null +++ b/agb/examples/affine_background.rs @@ -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 = 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); + } +} diff --git a/agb/examples/affine_tiles.toml b/agb/examples/affine_tiles.toml new file mode 100644 index 00000000..65d608a2 --- /dev/null +++ b/agb/examples/affine_tiles.toml @@ -0,0 +1,6 @@ +version = "1.0" + +[image.water_tiles] +filename = "water_tiles.png" +tile_size = "8x8" +colours = 256 \ No newline at end of file diff --git a/agb/examples/animated_background.rs b/agb/examples/animated_background.rs index d20213d9..6d547ac0 100644 --- a/agb/examples/animated_background.rs +++ b/agb/examples/animated_background.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting}, + tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, TiledMap}, Priority, }, include_gfx, @@ -18,7 +18,7 @@ fn main(mut gba: agb::Gba) -> ! { 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); diff --git a/agb/examples/chicken.rs b/agb/examples/chicken.rs index cc028776..0ce0ebf4 100644 --- a/agb/examples/chicken.rs +++ b/agb/examples/chicken.rs @@ -2,7 +2,7 @@ #![no_main] use agb::{ - display::tiled::{TileFormat, TileSet, TileSetting}, + display::tiled::{TileFormat, TileSet, TileSetting, TiledMap}, display::{ object::{Object, ObjectController, Size, Sprite}, palette16::Palette16, diff --git a/agb/examples/dynamic_tiles.rs b/agb/examples/dynamic_tiles.rs index 53b013a4..171e3e0a 100644 --- a/agb/examples/dynamic_tiles.rs +++ b/agb/examples/dynamic_tiles.rs @@ -3,7 +3,7 @@ use agb::display::{ palette16::Palette16, - tiled::{RegularBackgroundSize, TileSetting}, + tiled::{RegularBackgroundSize, TileSetting, TiledMap}, Priority, }; diff --git a/agb/examples/mixer_32768.rs b/agb/examples/mixer_32768.rs index b10bf8d3..2d9f60d7 100644 --- a/agb/examples/mixer_32768.rs +++ b/agb/examples/mixer_32768.rs @@ -3,11 +3,11 @@ use agb::{ display::{ - tiled::{RegularBackgroundSize, RegularMap, TileSetting, VRamManager}, + tiled::{RegularBackgroundSize, RegularMap, TileSetting, TiledMap, VRamManager}, Font, Priority, }, include_font, include_wav, - sound::mixer::SoundChannel, + sound::mixer::{Frequency, SoundChannel}, Gba, }; @@ -40,7 +40,7 @@ fn main(mut gba: Gba) -> ! { let mut timer = timer_controller.timer2; timer.set_enabled(true); - let mut mixer = gba.mixer.mixer(); + let mut mixer = gba.mixer.mixer(Frequency::Hz32768); mixer.enable(); let _interrupt = mixer.setup_interrupt_handler(); diff --git a/agb/examples/mixer_basic.rs b/agb/examples/mixer_basic.rs index 1419562b..e3fc76f9 100644 --- a/agb/examples/mixer_basic.rs +++ b/agb/examples/mixer_basic.rs @@ -3,7 +3,7 @@ use agb::fixnum::Num; use agb::input::{Button, ButtonController, Tri}; -use agb::sound::mixer::SoundChannel; +use agb::sound::mixer::{Frequency, SoundChannel}; use agb::{fixnum::num, include_wav, Gba}; // 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 vblank_provider = agb::interrupt::VBlank::get(); - let mut mixer = gba.mixer.mixer(); + let mut mixer = gba.mixer.mixer(Frequency::Hz10512); mixer.enable(); let channel = SoundChannel::new(DEAD_CODE); diff --git a/agb/examples/stereo_sound.rs b/agb/examples/stereo_sound.rs index d813a237..66dbcdee 100644 --- a/agb/examples/stereo_sound.rs +++ b/agb/examples/stereo_sound.rs @@ -3,11 +3,11 @@ use agb::{ display::{ - tiled::{RegularBackgroundSize, RegularMap, TileSetting, VRamManager}, + tiled::{RegularBackgroundSize, RegularMap, TileSetting, TiledMap, VRamManager}, Font, Priority, }, include_font, include_wav, - sound::mixer::SoundChannel, + sound::mixer::{Frequency, SoundChannel}, Gba, }; @@ -40,7 +40,7 @@ fn main(mut gba: Gba) -> ! { let mut timer = timer_controller.timer2; timer.set_enabled(true); - let mut mixer = gba.mixer.mixer(); + let mut mixer = gba.mixer.mixer(Frequency::Hz10512); mixer.enable(); let mut channel = SoundChannel::new(LET_IT_IN); diff --git a/agb/examples/text_render.rs b/agb/examples/text_render.rs index fa398a1d..79470d3d 100644 --- a/agb/examples/text_render.rs +++ b/agb/examples/text_render.rs @@ -3,7 +3,7 @@ use agb::{ display::{ - tiled::{RegularBackgroundSize, TileSetting}, + tiled::{RegularBackgroundSize, TileSetting, TiledMap}, Font, Priority, }, include_font, diff --git a/agb/gfx/agb_logo.toml b/agb/gfx/agb_logo.toml index eb1f3304..60787767 100644 --- a/agb/gfx/agb_logo.toml +++ b/agb/gfx/agb_logo.toml @@ -5,5 +5,4 @@ crate_prefix = "crate" [image.test_logo] filename = "test_logo.png" -transparent_colour = "010101" tile_size = "8x8" diff --git a/agb/src/agb_alloc/bump_allocator.rs b/agb/src/agb_alloc/bump_allocator.rs index 717ba45e..0ea27d2d 100644 --- a/agb/src/agb_alloc/bump_allocator.rs +++ b/agb/src/agb_alloc/bump_allocator.rs @@ -43,7 +43,7 @@ impl BumpAllocator { let resulting_ptr = ptr + amount_to_add; 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; } diff --git a/agb/src/display/example_logo.rs b/agb/src/display/example_logo.rs index 9b6dbe25..c6f57679 100644 --- a/agb/src/display/example_logo.rs +++ b/agb/src/display/example_logo.rs @@ -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"); 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); @@ -21,6 +21,7 @@ pub fn display_logo(map: &mut RegularMap, vram: &mut VRamManager) { map.commit(vram); map.show(); } + #[cfg(test)] mod tests { use crate::display::{tiled::RegularBackgroundSize, Priority}; diff --git a/agb/src/display/font.rs b/agb/src/display/font.rs index cb589ec3..ac105391 100644 --- a/agb/src/display/font.rs +++ b/agb/src/display/font.rs @@ -210,6 +210,7 @@ impl<'a> Drop for TextRenderer<'a> { #[cfg(test)] mod tests { use super::*; + use crate::display::tiled::TiledMap; const FONT: Font = crate::include_font!("examples/font/yoster.ttf", 12); #[test_case] diff --git a/agb/src/display/object.rs b/agb/src/display/object.rs index 2b6eaf23..8ba21cbf 100644 --- a/agb/src/display/object.rs +++ b/agb/src/display/object.rs @@ -19,7 +19,7 @@ use super::{Priority, DISPLAY_CONTROL}; use crate::agb_alloc::block_allocator::BlockAllocator; use crate::agb_alloc::bump_allocator::StartEnd; use crate::dma; -use crate::fixnum::Vector2D; +use crate::fixnum::{Num, Vector2D}; use crate::hash_map::HashMap; use attributes::*; @@ -724,7 +724,7 @@ impl ObjectController { unsafe { (OBJECT_ATTRIBUTE_MEMORY as *mut u16) - .add((i as usize) * 4) + .add(i * 4) .write_volatile(HIDDEN_VALUE); } @@ -871,7 +871,7 @@ impl ObjectController { let shape_size = sprite.sprite.0.size.shape_size(); attrs .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.a1a.set_size(shape_size.1); attrs.a1s.set_size(shape_size.1); @@ -887,7 +887,7 @@ impl ObjectController { }); let loan = Loan { - index: index as u8, + index, controller: self.inner, }; @@ -980,7 +980,7 @@ impl<'a> Object<'a> { object_inner .attrs .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.a1a.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 { { let mut object_inner = unsafe { self.object_inner() }; - object_inner.attrs.a1a.set_x(x.rem_euclid(1 << 9) as u16); - object_inner.attrs.a1s.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)); } self } @@ -1236,6 +1236,42 @@ enum ColourMode { 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, + /// Adjustment made to *X* coordinate along *vertical* lines. + /// Also known as "dmx". + /// Typically computed as `y_scale * sin(angle)`. + pub p_b: Num, + /// Adjustment made to *Y* coordinate along *horizontal* lines. + /// Also known as "dy". + /// Typically computed as `-x_scale * sin(angle)`. + pub p_c: Num, + /// Adjustment made to *Y* coordinate along *vertical* lines. + /// Also known as "dmy". + /// Typically computed as `y_scale * cos(angle)`. + pub p_d: Num, +} + +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. #[allow(dead_code)] mod attributes { @@ -1275,7 +1311,7 @@ mod attributes { pub(super) struct ObjectAttribute2 { pub tile_index: B10, pub priority: Priority, - pub palete_bank: B4, + pub palette_bank: B4, } } diff --git a/agb/src/display/tile_data.rs b/agb/src/display/tile_data.rs index 364b57c2..cc51ef9b 100644 --- a/agb/src/display/tile_data.rs +++ b/agb/src/display/tile_data.rs @@ -1,20 +1,13 @@ -use crate::display::palette16::Palette16; - +#[non_exhaustive] pub struct TileData { - pub palettes: &'static [Palette16], pub tiles: &'static [u8], pub palette_assignments: &'static [u8], } impl TileData { #[must_use] - pub const fn new( - palettes: &'static [Palette16], - tiles: &'static [u8], - palette_assignments: &'static [u8], - ) -> Self { + pub const fn new(tiles: &'static [u8], palette_assignments: &'static [u8]) -> Self { TileData { - palettes, tiles, palette_assignments, } diff --git a/agb/src/display/tiled/infinite_scrolled_map.rs b/agb/src/display/tiled/infinite_scrolled_map.rs index 4021494b..ed30997d 100644 --- a/agb/src/display/tiled/infinite_scrolled_map.rs +++ b/agb/src/display/tiled/infinite_scrolled_map.rs @@ -1,6 +1,9 @@ use alloc::boxed::Box; -use super::{BackgroundID, MapLoan, RegularMap, TileSet, TileSetting, VRamManager}; +use super::{ + BackgroundID, BackgroundSizePrivate, MapLoan, RegularMap, TileSet, TileSetting, TiledMap, + VRamManager, +}; use crate::{ display, @@ -157,7 +160,7 @@ impl<'a> InfiniteScrolledMap<'a> { /// # ); /// # /// # 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); /// backdrop.init(&mut vram, start_position, &mut || { /// vblank.wait_for_vblank(); @@ -232,7 +235,7 @@ impl<'a> InfiniteScrolledMap<'a> { /// # ); /// # /// # 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); /// while backdrop.init_partial(&mut vram, start_position) == PartialUpdateStatus::Continue { /// 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 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(); let copy_from = self.copied_up_to; @@ -383,11 +382,7 @@ impl<'a> InfiniteScrolledMap<'a> { } let current_scroll = self.map.scroll_pos(); - let new_scroll = ( - size.px_offset_x(i32::from(current_scroll.x) + difference.x), - size.px_offset_y(i32::from(current_scroll.y) + difference.y), - ) - .into(); + let new_scroll = current_scroll + (difference.x as i16, difference.y as i16).into(); self.map.set_scroll_pos(new_scroll); diff --git a/agb/src/display/tiled/map.rs b/agb/src/display/tiled/map.rs index b245ab23..bfe3399e 100644 --- a/agb/src/display/tiled/map.rs +++ b/agb/src/display/tiled/map.rs @@ -2,31 +2,182 @@ use core::cell::RefCell; use core::ops::{Deref, DerefMut}; 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::fixnum::Vector2D; +use crate::fixnum::{Num, Vector2D}; 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}; +pub trait TiledMapTypes: private::Sealed { + type Size: BackgroundSize + Copy; +} + +trait TiledMapPrivate: TiledMapTypes { + type TileType: Into + 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; + fn set_scroll_pos(&mut self, new_pos: Vector2D); + + fn bg_control_register(&self) -> MemoryMapped { + 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; + fn set_scroll_pos(&mut self, pos: Vector2D); +} + +impl 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 { + TiledMapPrivate::scroll_pos(self) + } + + fn set_scroll_pos(&mut self, pos: Vector2D) { + TiledMapPrivate::set_scroll_pos(self, pos); + } +} + pub struct RegularMap { background_id: u8, - screenblock: u8, - x_scroll: u16, - y_scroll: u16, priority: Priority, + size: RegularBackgroundSize, + + scroll: Vector2D, tiles: Vec, tiles_dirty: bool, - - size: RegularBackgroundSize, } 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 { + self.scroll + } + fn set_scroll_pos(&mut self, new_pos: Vector2D) { + self.scroll = new_pos; + } +} + impl RegularMap { pub(crate) fn new( background_id: u8, @@ -36,16 +187,14 @@ impl RegularMap { ) -> Self { Self { background_id, - screenblock, - x_scroll: 0, - y_scroll: 0, priority, + size, + + scroll: Default::default(), tiles: vec![Default::default(); size.num_tiles()], tiles_dirty: true, - - size, } } @@ -56,11 +205,11 @@ impl RegularMap { tileset: &TileSet<'_>, 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() { - vram.remove_tile(old_tile.tile_index()); + vram.remove_tile(old_tile.into()); } let tile_index = tile_setting.index(); @@ -77,86 +226,147 @@ impl RegularMap { return; } - self.tiles[pos] = new_tile; - self.tiles_dirty = true; + self.tiles_mut()[pos] = new_tile; + *self.tiles_dirty() = true; } - pub fn clear(&mut self, vram: &mut VRamManager) { - 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) { - self.x_scroll = pos.x; - self.y_scroll = pos.y; - } - - #[must_use] - pub fn scroll_pos(&self) -> Vector2D { - (self.x_scroll, self.y_scroll).into() - } - - pub(crate) fn size(&self) -> RegularBackgroundSize { - self.size - } - - const fn bg_control_register(&self) -> MemoryMapped { - unsafe { MemoryMapped::new(0x0400_0008 + 2 * self.background_id as usize) } - } - - const fn bg_h_offset(&self) -> MemoryMapped { + fn x_register(&self) -> MemoryMapped { unsafe { MemoryMapped::new(0x0400_0010 + 4 * self.background_id as usize) } } - const fn bg_v_offset(&self) -> MemoryMapped { + fn y_register(&self) -> MemoryMapped { unsafe { MemoryMapped::new(0x0400_0012 + 4 * self.background_id as usize) } } +} - const fn screenblock_memory(&self) -> *mut u16 { - (0x0600_0000 + 0x1000 * self.screenblock as usize / 2) as *mut u16 +pub struct AffineMap { + background_id: u8, + screenblock: u8, + priority: Priority, + size: AffineBackgroundSize, + + scroll: Vector2D, + + transform: BgAffineSetData, + + tiles: Vec, + 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 { + self.scroll + } + fn set_scroll_pos(&mut self, new_pos: Vector2D) { + 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, + 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>>, + scale: impl Into>>, + rotation: impl Into>, + ) { + 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> { + unsafe { MemoryMapped::new(0x0400_0008 + 0x10 * self.background_id()) } + } + fn bg_y(&self) -> MemoryMapped> { + unsafe { MemoryMapped::new(0x0400_000c + 0x10 * self.background_id()) } + } + fn bg_affine_matrix(&self) -> MemoryMapped { + unsafe { MemoryMapped::new(0x0400_0000 + 0x10 * self.background_id()) } } } @@ -165,7 +375,7 @@ pub struct MapLoan<'a, T> { background_id: u8, screenblock_id: u8, screenblock_length: u8, - regular_map_list: &'a RefCell>, + map_list: &'a RefCell>, screenblock_list: &'a RefCell>, } @@ -189,7 +399,7 @@ impl<'a, T> MapLoan<'a, T> { background_id: u8, screenblock_id: u8, screenblock_length: u8, - regular_map_list: &'a RefCell>, + map_list: &'a RefCell>, screenblock_list: &'a RefCell>, ) -> Self { MapLoan { @@ -197,7 +407,7 @@ impl<'a, T> MapLoan<'a, T> { background_id, screenblock_id, screenblock_length, - regular_map_list, + map_list, screenblock_list, } } @@ -210,7 +420,7 @@ impl<'a, T> MapLoan<'a, T> { impl<'a, T> Drop for MapLoan<'a, T> { fn drop(&mut self) { - self.regular_map_list + self.map_list .borrow_mut() .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 {} +} diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs index 529b8681..ea23aa4a 100644 --- a/agb/src/display/tiled/mod.rs +++ b/agb/src/display/tiled/mod.rs @@ -1,28 +1,76 @@ mod infinite_scrolled_map; mod map; mod tiled0; +mod tiled1; +mod tiled2; mod vram_manager; +use crate::bitarray::Bitarray; +use crate::display::Priority; use agb_fixnum::Vector2D; +use core::cell::RefCell; 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 tiled1::Tiled1; +pub use tiled2::Tiled2; 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)] +#[repr(u16)] pub enum RegularBackgroundSize { - Background32x32, - Background64x32, - Background32x64, - Background64x64, + Background32x32 = 0, + Background64x32 = 1, + Background32x64 = 2, + Background64x64 = 3, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] 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] - 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) -> 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 { RegularBackgroundSize::Background32x64 | RegularBackgroundSize::Background32x32 => 32, RegularBackgroundSize::Background64x64 | RegularBackgroundSize::Background64x32 => 64, @@ -30,33 +78,26 @@ impl RegularBackgroundSize { } #[must_use] - pub fn height(&self) -> u32 { + fn height(&self) -> u32 { match self { RegularBackgroundSize::Background32x32 | RegularBackgroundSize::Background64x32 => 32, RegularBackgroundSize::Background32x64 | RegularBackgroundSize::Background64x64 => 64, } } +} - pub(crate) fn size_flag(self) -> u16 { - match self { - RegularBackgroundSize::Background32x32 => 0, - RegularBackgroundSize::Background64x32 => 1, - RegularBackgroundSize::Background32x64 => 2, - RegularBackgroundSize::Background64x64 => 3, - } +impl BackgroundSizePrivate for RegularBackgroundSize { + fn size_flag(self) -> u16 { + self as u16 } - pub(crate) fn num_tiles(self) -> usize { - (self.width() * self.height()) as usize - } - - pub(crate) fn num_screen_blocks(self) -> usize { + fn num_screen_blocks(&self) -> usize { self.num_tiles() / (32 * 32) } // 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 - pub(crate) fn gba_offset(self, pos: Vector2D) -> usize { + fn gba_offset(&self, pos: Vector2D) -> usize { let x_mod = pos.x & (self.width() as u16 - 1); let y_mod = pos.y & (self.height() as u16 - 1); @@ -66,21 +107,43 @@ impl RegularBackgroundSize { pos as usize } +} - pub(crate) fn tile_pos_x(self, x: i32) -> u16 { - ((x as u32) & (self.width() - 1)) as u16 +impl BackgroundSize for AffineBackgroundSize { + #[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 { - ((y as u32) & (self.height() - 1)) as u16 + #[must_use] + 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 { - ((x as u32) & (self.width() * 8 - 1)) as u16 + fn num_screen_blocks(&self) -> usize { + // 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 { - ((y as u32) & (self.height() * 8 - 1)) as u16 + // Affine modes don't do the convoluted staggered block layout + fn gba_offset(&self, pos: Vector2D) -> 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 { fn new(idx: TileIndex, setting: TileSetting) -> Self { - Self(idx.index() | setting.setting()) + Self(idx.raw_index() | setting.setting()) } 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>; +} + +trait CreatableRegularTiledMode: TiledMode { + const REGULAR_BACKGROUNDS: usize; + fn regular(&self) -> &RefCell>; +} + +trait CreatableAffineTiledMode: TiledMode { + const AFFINE_BACKGROUNDS: usize; + fn affine(&self) -> &RefCell>; +} + +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 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 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)] mod test { use super::*; diff --git a/agb/src/display/tiled/tiled0.rs b/agb/src/display/tiled/tiled0.rs index fbc523e2..66ff0054 100644 --- a/agb/src/display/tiled/tiled0.rs +++ b/agb/src/display/tiled/tiled0.rs @@ -1,12 +1,14 @@ use core::cell::RefCell; +use super::{ + CreatableRegularTiledMode, MapLoan, RegularBackgroundSize, RegularMap, RegularTiledMode, + TiledMode, +}; use crate::{ bitarray::Bitarray, display::{set_graphics_mode, DisplayMode, Priority}, }; -use super::{MapLoan, RegularBackgroundSize, RegularMap}; - pub struct Tiled0 { regular: RefCell>, screenblocks: RefCell>, @@ -27,52 +29,20 @@ impl Tiled0 { priority: Priority, size: RegularBackgroundSize, ) -> MapLoan<'_, RegularMap> { - let mut regular = self.regular.borrow_mut(); - 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, - ) + self.regular_background(priority, size) } } -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; +impl TiledMode for Tiled0 { + fn screenblocks(&self) -> &RefCell> { + &self.screenblocks + } +} + +impl CreatableRegularTiledMode for Tiled0 { + const REGULAR_BACKGROUNDS: usize = 4; + + fn regular(&self) -> &RefCell> { + &self.regular } - - panic!( - "Failed to find screenblock gap of at least {} elements", - gap - ); } diff --git a/agb/src/display/tiled/tiled1.rs b/agb/src/display/tiled/tiled1.rs new file mode 100644 index 00000000..84903aa2 --- /dev/null +++ b/agb/src/display/tiled/tiled1.rs @@ -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>, + affine: RefCell>, + screenblocks: RefCell>, +} + +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> { + &self.screenblocks + } +} + +impl CreatableRegularTiledMode for Tiled1 { + const REGULAR_BACKGROUNDS: usize = 2; + + fn regular(&self) -> &RefCell> { + &self.regular + } +} + +impl CreatableAffineTiledMode for Tiled1 { + const AFFINE_BACKGROUNDS: usize = 1; + + fn affine(&self) -> &RefCell> { + &self.affine + } +} diff --git a/agb/src/display/tiled/tiled2.rs b/agb/src/display/tiled/tiled2.rs new file mode 100644 index 00000000..bcef9f31 --- /dev/null +++ b/agb/src/display/tiled/tiled2.rs @@ -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>, + screenblocks: RefCell>, +} + +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> { + &self.screenblocks + } +} + +impl CreatableAffineTiledMode for Tiled2 { + const AFFINE_BACKGROUNDS: usize = 2; + + fn affine(&self) -> &RefCell> { + &self.affine + } +} diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs index 6caa8956..3464f54e 100644 --- a/agb/src/display/tiled/vram_manager.rs +++ b/agb/src/display/tiled/vram_manager.rs @@ -2,6 +2,7 @@ use core::{alloc::Layout, ptr::NonNull}; use alloc::{slice, vec::Vec}; +use crate::display::tiled::Tile; use crate::{ agb_alloc::{block_allocator::BlockAllocator, bump_allocator::StartEnd}, 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 { FourBpp, + EightBpp, } impl TileFormat { /// Returns the size of the tile in bytes - fn tile_size(self) -> usize { + pub(crate) const fn tile_size(self) -> usize { match self { TileFormat::FourBpp => 8 * 8 / 2, + TileFormat::EightBpp => 8 * 8, } } } @@ -55,15 +60,50 @@ impl<'a> TileSet<'a> { } #[derive(Debug, Clone, Copy)] -pub struct TileIndex(u16); +pub enum TileIndex { + FourBpp(u16), + EightBpp(u8), +} impl TileIndex { - pub(crate) const fn new(index: usize) -> Self { - Self(index as u16) + pub(crate) const fn new(index: usize, format: TileFormat) -> Self { + match format { + TileFormat::FourBpp => Self::FourBpp(index as u16), + TileFormat::EightBpp => Self::EightBpp(index as u8), + } } - pub(crate) const fn index(self) -> u16 { - self.0 + pub(crate) const fn raw_index(self) -> u16 { + 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 for TileIndex { + fn from(tile: Tile) -> Self { + tile.tile_index() + } +} + +impl From for TileIndex { + fn from(index: u8) -> TileIndex { + TileIndex::new(usize::from(index), TileFormat::EightBpp) } } @@ -159,7 +199,7 @@ impl DynamicTile<'_> { #[must_use] pub fn tile_index(&self) -> u16 { 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; - difference / (8 * 8 / 2) + TileIndex::new(difference / format.tile_size(), format) } 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()) } #[must_use] pub fn new_dynamic_tile<'a>(&mut self) -> DynamicTile<'a> { + // TODO: format param? let tile_format = TileFormat::FourBpp; - let new_reference: NonNull = - unsafe { TILE_ALLOCATOR.alloc(TILE_LAYOUT) }.unwrap().cast(); + let new_reference: NonNull = unsafe { TILE_ALLOCATOR.alloc(layout_of(tile_format)) } + .unwrap() + .cast(); 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 { 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); self.tile_set_to_vram.insert( - TileInTileSetReference::new(&tile_set, index as u16), + TileInTileSetReference::new(&tile_set, index.raw_index()), tile_reference, ); - self.reference_counts.resize( - self.reference_counts.len().max(index + 1), - Default::default(), - ); - self.reference_counts[index] = - TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index as u16)); + self.reference_counts + .resize(self.reference_counts.len().max(key + 1), Default::default()); + self.reference_counts[key] = + TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index.raw_index())); DynamicTile { tile_data: unsafe { slice::from_raw_parts_mut( tiles .as_mut_ptr() - .add((index * tile_format.tile_size()) as usize) + .add(index.raw_index() as usize * tile_format.tile_size()) .cast(), tile_format.tile_size() / core::mem::size_of::(), ) @@ -238,8 +279,9 @@ impl VRamManager { let pointer = NonNull::new(dynamic_tile.tile_data.as_mut_ptr() as *mut _).unwrap(); let tile_reference = TileReference(pointer); - let tile_index = Self::index_from_reference(tile_reference); - self.remove_tile(TileIndex::new(tile_index)); + // TODO: dynamic_tile.format? + 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 { @@ -248,37 +290,39 @@ impl VRamManager { .get(&TileInTileSetReference::new(tile_set, tile)); if let Some(reference) = reference { - let index = Self::index_from_reference(*reference); - self.reference_counts[index].increment_reference_count(); - return TileIndex::new(index); + let tile_index = Self::index_from_reference(*reference, tile_set.format); + let key = tile_index.refcount_key(); + self.reference_counts[key].increment_reference_count(); + return tile_index; } let new_reference: NonNull = - 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); 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 .insert(TileInTileSetReference::new(tile_set, tile), tile_reference); - self.reference_counts.resize( - self.reference_counts.len().max(index + 1), - Default::default(), - ); + self.reference_counts + .resize(self.reference_counts.len().max(key + 1), Default::default()); - self.reference_counts[index] = + self.reference_counts[key] = TileReferenceCount::new(TileInTileSetReference::new(tile_set, tile)); - TileIndex::new(index) + index } 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 { return; @@ -289,23 +333,26 @@ impl VRamManager { pub(crate) fn gc(&mut self) { for tile_index in self.indices_to_gc.drain(..) { - let index = tile_index.index() as usize; - if self.reference_counts[index].current_count() > 0 { + let key = tile_index.refcount_key(); + if self.reference_counts[key].current_count() > 0 { continue; // it has since been added back } let tile_reference = Self::reference_from_index(tile_index); 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 .as_ref() .unwrap(); 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: u16, ) { + assert_eq!( + source_tile_set.format, target_tile_set.format, + "Must replace a tileset with the same format" + ); + if let Some(&reference) = self .tile_set_to_vram .get(&TileInTileSetReference::new(source_tile_set, source_tile)) diff --git a/agb/src/display/video.rs b/agb/src/display/video.rs index 9f62e3fa..d0edf2e1 100644 --- a/agb/src/display/video.rs +++ b/agb/src/display/video.rs @@ -1,7 +1,7 @@ use super::{ bitmap3::Bitmap3, bitmap4::Bitmap4, - tiled::{Tiled0, VRamManager}, + tiled::{Tiled0, Tiled1, Tiled2, VRamManager}, }; /// The video struct controls access to the video hardware. @@ -26,4 +26,14 @@ impl Video { pub fn tiled0(&mut self) -> (Tiled0, VRamManager) { (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()) + } } diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 764cdbfa..693717f9 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -81,7 +81,7 @@ /// # /// use agb::{ /// display::{ -/// tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, VRamManager}, +/// tiled::{RegularBackgroundSize, TileFormat, TileSet, TileSetting, Tiled0, TiledMap, VRamManager}, /// Priority, /// }, /// include_gfx, @@ -92,7 +92,7 @@ /// # fn load_tileset(mut gfx: Tiled0, mut vram: VRamManager) { /// 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); /// diff --git a/agb/src/memory_mapped.rs b/agb/src/memory_mapped.rs index 290d865f..617dc48d 100644 --- a/agb/src/memory_mapped.rs +++ b/agb/src/memory_mapped.rs @@ -16,7 +16,9 @@ impl MemoryMapped { } pub fn set(&self, val: T) { - unsafe { self.address.write_volatile(val) } + if core::mem::size_of::() != 0 { + unsafe { self.address.write_volatile(val) } + } } } diff --git a/agb/src/save/flash.rs b/agb/src/save/flash.rs index 0384926b..28b21e50 100644 --- a/agb/src/save/flash.rs +++ b/agb/src/save/flash.rs @@ -51,7 +51,7 @@ fn set_bank(bank: u8) -> Result<(), Error> { Err(Error::OutOfBounds) } else if bank != CURRENT_BANK.read() { issue_flash_command(CMD_SET_BANK); - FLASH_PORT_BANK.set(bank as u8); + FLASH_PORT_BANK.set(bank); CURRENT_BANK.write(bank); Ok(()) } else { diff --git a/agb/src/sound/mixer/mod.rs b/agb/src/sound/mixer/mod.rs index ac01dba7..64b82c46 100644 --- a/agb/src/sound/mixer/mod.rs +++ b/agb/src/sound/mixer/mod.rs @@ -14,17 +14,11 @@ //! //! # Concepts //! -//! The mixer runs at a fixed frequency which is determined at compile time by enabling -//! certain features within the crate. The following features are currently available: -//! -//! | Feature | Frequency | -//! |---------|-----------| -//! | none | 10512Hz | -//! | freq18157 | 18157Hz | -//! | freq32768[^32768Hz] | 32768Hz | +//! The mixer runs at a fixed frequency which is determined at initialisation time by +//! passing certain [`Frequency`] options. //! //! 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 //! effects applied to them (such as changing the speed at which they play or the panning). @@ -38,12 +32,17 @@ //! ```rust,no_run //! # #![no_std] //! # #![no_main] +//! use agb::sound::mixer::Frequency; //! # fn foo(gba: &mut agb::Gba) { -//! let mut mixer = gba.mixer.mixer(); +//! let mut mixer = gba.mixer.mixer(Frequency::Hz10512); //! 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 //! //! Then, you have a choice of whether you want to use interrupts or do the buffer swapping @@ -55,9 +54,10 @@ //! ```rust,no_run //! # #![no_std] //! # #![no_main] +//! use agb::sound::mixer::Frequency; //! # fn foo(gba: &mut agb::Gba) { -//! # let mut mixer = gba.mixer.mixer(); -//! # let vblank = agb::interrupt::VBlank::get(); +//! let mut mixer = gba.mixer.mixer(Frequency::Hz10512); +//! let vblank = agb::interrupt::VBlank::get(); //! // Somewhere in your main loop: //! mixer.frame(); //! vblank.wait_for_vblank(); @@ -70,10 +70,13 @@ //! ```rust,no_run //! # #![no_std] //! # #![no_main] +//! use agb::sound::mixer::Frequency; //! # fn foo(gba: &mut agb::Gba) { -//! # let mut mixer = gba.mixer.mixer(); -//! # let vblank = agb::interrupt::VBlank::get(); +//! let mut mixer = gba.mixer.mixer(agb::sound::mixer::Frequency::Hz32768); +//! let vblank = agb::interrupt::VBlank::get(); //! // 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(); //! //! // inside your main loop @@ -99,7 +102,7 @@ //! # #![no_std] //! # #![no_main] //! # 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(); //! # use agb::{*, sound::mixer::*}; //! // 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 //! it has finished. -//! -//! [^32768Hz]: You must use interrupts when using 32768Hz - mod hw; mod sw_mixer; @@ -138,8 +138,8 @@ impl MixerController { } /// Get a [`Mixer`] in order to start producing sounds. - pub fn mixer(&mut self) -> Mixer { - Mixer::new() + pub fn mixer(&mut self, frequency: Frequency) -> Mixer { + Mixer::new(frequency) } } @@ -149,6 +149,43 @@ enum SoundPriority { 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 /// the background music. Use the factory methods on this to modify how it is played. /// @@ -185,7 +222,7 @@ enum SoundPriority { /// /// // somewhere in code /// # 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); /// bgm.stereo().should_loop(); /// let _ = mixer.play_sound(bgm); @@ -204,7 +241,7 @@ enum SoundPriority { /// /// // somewhere in code /// # 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 _ = mixer.play_sound(jump_sound); /// # } @@ -241,7 +278,7 @@ impl SoundChannel { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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: /// const JUMP_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav"); /// @@ -283,7 +320,7 @@ impl SoundChannel { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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: /// const MY_BGM: &[u8] = include_wav!("examples/sfx/my_bgm.wav"); /// diff --git a/agb/src/sound/mixer/sw_mixer.rs b/agb/src/sound/mixer/sw_mixer.rs index 80b5b139..44c17c4f 100644 --- a/agb/src/sound/mixer/sw_mixer.rs +++ b/agb/src/sound/mixer/sw_mixer.rs @@ -1,12 +1,14 @@ use core::cell::RefCell; -use core::intrinsics::transmute; +use alloc::boxed::Box; +use alloc::vec::Vec; use bare_metal::{CriticalSection, Mutex}; -use super::hw; use super::hw::LeftOrRight; +use super::{hw, Frequency}; use super::{SoundChannel, SoundPriority}; +use crate::InternalAllocator; use crate::{ fixnum::Num, interrupt::free, @@ -46,7 +48,7 @@ extern "C" { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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::*; /// # 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(); /// // Outside your main function in global scope: /// const MY_CRAZY_SOUND: &[u8] = include_wav!("examples/sfx/jump.wav"); /// /// // 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); /// channel.stereo(); /// let _ = mixer.play_sound(channel); @@ -80,6 +82,7 @@ pub struct Mixer { buffer: MixerBuffer, channels: [Option; 8], indices: [i32; 8], + frequency: Frequency, timer: Timer, } @@ -96,7 +99,7 @@ pub struct Mixer { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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"); /// 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 @@ -108,9 +111,10 @@ pub struct Mixer { pub struct ChannelId(usize, i32); impl Mixer { - pub(super) fn new() -> Self { + pub(super) fn new(frequency: Frequency) -> Self { Self { - buffer: MixerBuffer::new(), + frequency, + buffer: MixerBuffer::new(frequency), channels: 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 /// 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) { - 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(); } @@ -138,7 +142,7 @@ impl Mixer { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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(); /// loop { /// mixer.frame(); @@ -167,7 +171,7 @@ impl Mixer { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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(); /// // you must set this to a named variable to ensure that the scope is long enough /// let _mixer_interrupt = mixer.setup_interrupt_handler(); @@ -184,7 +188,7 @@ impl Mixer { .set_cascade(true) .set_divider(Divider::Divider1) .set_interrupt(true) - .set_overflow_amount(constants::SOUND_BUFFER_SIZE as u16) + .set_overflow_amount(self.frequency.buffer_size() as u16) .set_enabled(true); add_interrupt_handler(timer1.interrupt(), move |cs| self.buffer.swap(cs)) @@ -205,7 +209,7 @@ impl Mixer { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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(); /// loop { /// mixer.frame(); @@ -244,7 +248,7 @@ impl Mixer { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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"); /// 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 @@ -293,7 +297,7 @@ impl Mixer { /// # use agb::sound::mixer::*; /// # use agb::*; /// # 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"); /// 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 @@ -313,47 +317,32 @@ impl Mixer { } } -// These work perfectly with swapping the buffers every vblank -// 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() { +fn set_asm_buffer_size(frequency: Frequency) { extern "C" { static mut agb_rs__buffer_size: usize; } unsafe { - agb_rs__buffer_size = constants::SOUND_BUFFER_SIZE; + agb_rs__buffer_size = frequency.buffer_size(); } } -#[repr(C, align(4))] -struct SoundBuffer([i8; constants::SOUND_BUFFER_SIZE * 2]); +struct SoundBuffer(Box<[i8], InternalAllocator>); -impl Default for SoundBuffer { - fn default() -> Self { - Self([0; constants::SOUND_BUFFER_SIZE * 2]) +impl SoundBuffer { + fn new(frequency: Frequency) -> Self { + 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 { buffers: [SoundBuffer; 3], + working_buffer: Box<[Num], InternalAllocator>, + frequency: Frequency, state: Mutex>, } @@ -391,16 +380,26 @@ impl MixerBufferState { } impl MixerBuffer { - fn new() -> Self { - set_asm_buffer_size(); + fn new(frequency: Frequency) -> Self { + let mut working_buffer = + Vec::with_capacity_in(frequency.buffer_size() * 2, InternalAllocator); + working_buffer.resize(frequency.buffer_size() * 2, 0.into()); 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 { active_buffer: 0, playing_buffer: 0, })), + + frequency, } } @@ -413,15 +412,16 @@ impl MixerBuffer { let (left_buffer, right_buffer) = self.buffers[buffer] .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(right_buffer, LeftOrRight::Right); } fn write_channels<'a>(&mut self, channels: impl Iterator) { - let mut buffer: [Num; constants::SOUND_BUFFER_SIZE * 2] = - unsafe { transmute([0i16; constants::SOUND_BUFFER_SIZE * 2]) }; + set_asm_buffer_size(self.frequency); + + self.working_buffer.fill(0.into()); for channel in channels { if channel.is_done { @@ -434,7 +434,7 @@ impl MixerBuffer { 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() { // TODO: This should probably play what's left rather than skip the last bit @@ -450,7 +450,7 @@ impl MixerBuffer { unsafe { agb_rs__mixer_add_stereo( channel.data.as_ptr().add(channel.pos.floor()), - buffer.as_mut_ptr(), + self.working_buffer.as_mut_ptr(), channel.volume, ); } @@ -461,7 +461,7 @@ impl MixerBuffer { unsafe { agb_rs__mixer_add( channel.data.as_ptr().add(channel.pos.floor()), - buffer.as_mut_ptr(), + self.working_buffer.as_mut_ptr(), playback_speed, left_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()); @@ -477,7 +477,7 @@ impl MixerBuffer { let write_buffer = &mut self.buffers[write_buffer_index].0; 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()); } } } diff --git a/agb/src/syscall.rs b/agb/src/syscall.rs index 69ff353f..3ab60830 100644 --- a/agb/src/syscall.rs +++ b/agb/src/syscall.rs @@ -1,11 +1,14 @@ +use agb_fixnum::Vector2D; 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)] const fn swi_map(thumb_id: u32) -> u32 { - if cfg!(target_feature="thumb-mode") { + if cfg!(target_feature = "thumb-mode") { thumb_id } else { thumb_id << 16 @@ -136,55 +139,125 @@ pub fn arc_tan2(x: i16, y: i32) -> i16 { result } -// pub fn affine_matrix( -// x_scale: Num, -// y_scale: Num, -// rotation: u8, -// ) -> AffineMatrixAttributes { -// let mut result = AffineMatrixAttributes { -// p_a: 0, -// p_b: 0, -// p_c: 0, -// p_d: 0, -// }; +#[repr(C, packed(4))] +pub struct BgAffineSetData { + pub matrix: AffineMatrixAttributes, + pub position: Vector2D>, +} +impl Default for BgAffineSetData { + fn default() -> Self { + Self { + matrix: AffineMatrixAttributes::default(), + position: (0, 0).into(), + } + } +} -// #[allow(dead_code)] -// #[repr(C, packed)] -// struct Input { -// x_scale: i16, -// y_scale: i16, -// rotation: u16, -// } +/// `rotation` is in revolutions. +#[must_use] +pub fn bg_affine_matrix( + bg_center: Vector2D>, + display_center: Vector2D, + scale: Vector2D>, + rotation: Num, +) -> BgAffineSetData { + #[repr(C, packed(4))] + struct Input { + bg_center: Vector2D>, + display_center: Vector2D, + scale: Vector2D>, + rotation: Num, + } -// let input = Input { -// y_scale: x_scale.to_raw(), -// x_scale: y_scale.to_raw(), -// rotation: rotation as u16, -// }; + let input = Input { + bg_center, + display_center, + scale, + rotation, + }; -// unsafe { -// 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, -// ) -// } + let mut output = MaybeUninit::uninit(); -// 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)] -// mod tests { -// use super::*; + clobber_abi("C") + ); + } -// #[test_case] -// fn affine(_gba: &mut crate::Gba) { -// // expect identity matrix -// let one: Num = 1.into(); + unsafe { output.assume_init() } +} -// let aff = affine_matrix(one, one, 0); -// assert_eq!(aff.p_a, one.to_raw()); -// assert_eq!(aff.p_d, one.to_raw()); -// } -// } +/// `rotation` is in revolutions. +#[must_use] +pub fn obj_affine_matrix( + scale: Vector2D>, + rotation: Num, +) -> AffineMatrixAttributes { + #[allow(dead_code)] + #[repr(C, packed(4))] + struct Input { + scale: Vector2D>, + 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 = 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()); + } +} diff --git a/examples/hyperspace-roll/Cargo.toml b/examples/hyperspace-roll/Cargo.toml index 76c3f4bc..317e2525 100644 --- a/examples/hyperspace-roll/Cargo.toml +++ b/examples/hyperspace-roll/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -agb = { version = "0.11.1", path = "../../agb", features = ["freq32768"] } +agb = { version = "0.11.1", path = "../../agb" } bare-metal = "1" [profile.dev] diff --git a/examples/hyperspace-roll/build.rs b/examples/hyperspace-roll/build.rs index e71fdf55..f328e4d9 100644 --- a/examples/hyperspace-roll/build.rs +++ b/examples/hyperspace-roll/build.rs @@ -1 +1 @@ -fn main() {} \ No newline at end of file +fn main() {} diff --git a/examples/hyperspace-roll/gfx/descriptions.toml b/examples/hyperspace-roll/gfx/backgrounds.toml similarity index 51% rename from examples/hyperspace-roll/gfx/descriptions.toml rename to examples/hyperspace-roll/gfx/backgrounds.toml index 9ce038e0..6fe2df1e 100644 --- a/examples/hyperspace-roll/gfx/descriptions.toml +++ b/examples/hyperspace-roll/gfx/backgrounds.toml @@ -1,11 +1,22 @@ 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] filename = "descriptions1.png" tile_size = "8x8" -transparent_colour = "121105" [image.descriptions2] filename = "descriptions2.png" tile_size = "8x8" -transparent_colour = "121105" \ No newline at end of file diff --git a/examples/hyperspace-roll/gfx/help.toml b/examples/hyperspace-roll/gfx/help.toml deleted file mode 100644 index 149b68d9..00000000 --- a/examples/hyperspace-roll/gfx/help.toml +++ /dev/null @@ -1,6 +0,0 @@ -version = "1.0" - -[image.help] -filename = "help-text.png" -tile_size = "8x8" -transparent_colour = "121105" diff --git a/examples/hyperspace-roll/gfx/stars.toml b/examples/hyperspace-roll/gfx/stars.toml deleted file mode 100644 index 354d1df4..00000000 --- a/examples/hyperspace-roll/gfx/stars.toml +++ /dev/null @@ -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" \ No newline at end of file diff --git a/examples/hyperspace-roll/src/background.rs b/examples/hyperspace-roll/src/background.rs index 91d16c95..3c3d6931 100644 --- a/examples/hyperspace-roll/src/background.rs +++ b/examples/hyperspace-roll/src/background.rs @@ -1,21 +1,14 @@ use agb::{ - display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, + display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager}, include_gfx, rng, }; use crate::sfx::Sfx; -include_gfx!("gfx/stars.toml"); - -include_gfx!("gfx/help.toml"); +include_gfx!("gfx/backgrounds.toml"); pub fn load_palettes(vram: &mut VRamManager) { - vram.set_background_palettes(&[ - stars::stars.palettes[0].clone(), - crate::customise::DESCRIPTIONS_1_PALETTE.clone(), - crate::customise::DESCRIPTIONS_2_PALETTE.clone(), - help::help.palettes[0].clone(), - ]); + vram.set_background_palettes(backgrounds::PALETTES); } pub(crate) fn load_help_text( @@ -24,42 +17,95 @@ pub(crate) fn load_help_text( help_text_line: 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 { + let tile_id = help_text_line * 16 + x; + background.set_tile( vram, (x + at_tile.0, at_tile.1).into(), &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 fn create_background_map(map: &mut RegularMap, vram: &mut VRamManager, stars_tileset: &TileSet) { for x in 0..64u16 { for y in 0..32u16 { let blank = rng::gen().rem_euclid(32) < 30; - let tile_id = if blank { - (1 << 10) - 1 + let (tile_id, palette_id) = if blank { + ((1 << 10) - 1, 0) } 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_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) { - background.set_scroll_pos((0_u16, 0_u16).into()); - vram.set_background_palettes(stars::title.palettes); - let tile_set = TileSet::new(stars::title.tiles, agb::display::tiled::TileFormat::FourBpp); + background.set_scroll_pos((0i16, 0).into()); + vram.set_background_palettes(backgrounds::PALETTES); + let tile_set = TileSet::new( + backgrounds::title.tiles, + agb::display::tiled::TileFormat::FourBpp, + ); background.hide(); for x in 0..30u16 { @@ -73,7 +119,7 @@ pub fn show_title_screen(background: &mut RegularMap, vram: &mut VRamManager, sf tile_id, 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, vram: &'_ mut VRamManager, ) -> 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(background2, vram, &stars_tileset); @@ -116,13 +162,13 @@ impl<'a> StarBackground<'a> { pub fn update(&mut self) { if self.background1_timer == 0 { 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; } if self.background2_timer == 0 { 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; } diff --git a/examples/hyperspace-roll/src/battle.rs b/examples/hyperspace-roll/src/battle.rs index 3fd3b870..04f76fdd 100644 --- a/examples/hyperspace-roll/src/battle.rs +++ b/examples/hyperspace-roll/src/battle.rs @@ -2,7 +2,7 @@ use crate::sfx::Sfx; use crate::{ 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 alloc::vec; use alloc::vec::Vec; @@ -207,7 +207,7 @@ impl RolledDice { let heal = *face_counts.entry(Face::Heal).or_default(); if heal != 0 { 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.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, 2, (0, 1)); diff --git a/examples/hyperspace-roll/src/customise.rs b/examples/hyperspace-roll/src/customise.rs index 852ff5a5..ac0e9a96 100644 --- a/examples/hyperspace-roll/src/customise.rs +++ b/examples/hyperspace-roll/src/customise.rs @@ -1,26 +1,20 @@ use agb::{ display::{ object::{Object, ObjectController}, - palette16::Palette16, - tiled::{RegularMap, TileSet, TileSetting}, + tiled::{RegularMap, TiledMap}, HEIGHT, WIDTH, }, - include_gfx, input::{Button, Tri}, }; use alloc::vec::Vec; use crate::{ + background::load_description, graphics::{FACE_SPRITES, MODIFIED_BOX, SELECTED_BOX, SELECT_BOX}, 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 { Dice, Face, @@ -169,20 +163,11 @@ pub(crate) fn customise_screen( ) -> PlayerDice { agb.sfx.customise(); 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)); - 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 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 cursor.upgrade != old_updade { - for y in 0..11 { - for x in 0..8 { - if (upgrades[cursor.upgrade] as usize) < 10 { - descriptions_map.set_tile( - &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, - ), - ) - } - } - } + load_description( + upgrades[cursor.upgrade] as usize, + descriptions_map, + &mut agb.vram, + ); } descriptions_map.show(); } else { diff --git a/examples/hyperspace-roll/src/main.rs b/examples/hyperspace-roll/src/main.rs index 9ec4cc1a..cd61df84 100644 --- a/examples/hyperspace-roll/src/main.rs +++ b/examples/hyperspace-roll/src/main.rs @@ -10,11 +10,11 @@ // which won't be a particularly clear error message. #![no_main] -use agb::display; use agb::display::object::ObjectController; -use agb::display::tiled::VRamManager; +use agb::display::tiled::{TiledMap, VRamManager}; use agb::display::Priority; use agb::interrupt::VBlank; +use agb::{display, sound::mixer::Frequency}; extern crate alloc; 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); star_background.commit(&mut vram); - let mut mixer = gba.mixer.mixer(); + let mut mixer = gba.mixer.mixer(Frequency::Hz32768); mixer.enable(); let _interrupt_handler = mixer.setup_interrupt_handler(); diff --git a/examples/the-hat-chooses-the-wizard/gfx/splash_screens.toml b/examples/the-hat-chooses-the-wizard/gfx/splash_screens.toml index 30156f38..fea8a855 100644 --- a/examples/the-hat-chooses-the-wizard/gfx/splash_screens.toml +++ b/examples/the-hat-chooses-the-wizard/gfx/splash_screens.toml @@ -1,11 +1,11 @@ version = "1.0" +transparent_colour = "2ce8f4" + [image.thanks_for_playing] filename = "thanks_for_playing.png" tile_size = "8x8" -transparent_colour = "2ce8f4" [image.splash] filename = "splash.png" tile_size = "8x8" -transparent_colour = "2ce8f4" diff --git a/examples/the-hat-chooses-the-wizard/gfx/tile_sheet.toml b/examples/the-hat-chooses-the-wizard/gfx/tile_sheet.toml index 373508d5..4ef16fc0 100644 --- a/examples/the-hat-chooses-the-wizard/gfx/tile_sheet.toml +++ b/examples/the-hat-chooses-the-wizard/gfx/tile_sheet.toml @@ -1,7 +1,7 @@ version = "1.0" +transparent_colour = "2ce8f4" + [image.background] filename = "tile_sheet.png" tile_size = "8x8" -transparent_colour = "2ce8f4" - diff --git a/examples/the-hat-chooses-the-wizard/src/level_display.rs b/examples/the-hat-chooses-the-wizard/src/level_display.rs index 990e7635..0defcc31 100644 --- a/examples/the-hat-chooses-the-wizard/src/level_display.rs +++ b/examples/the-hat-chooses-the-wizard/src/level_display.rs @@ -1,5 +1,5 @@ use agb::display::{ - tiled::{RegularMap, TileSet, TileSetting, VRamManager}, + tiled::{RegularMap, TileSet, TileSetting, TiledMap, VRamManager}, 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()); } diff --git a/examples/the-hat-chooses-the-wizard/src/main.rs b/examples/the-hat-chooses-the-wizard/src/main.rs index 133afb4e..b69c1a48 100644 --- a/examples/the-hat-chooses-the-wizard/src/main.rs +++ b/examples/the-hat-chooses-the-wizard/src/main.rs @@ -11,12 +11,13 @@ use agb::{ object::{Graphics, Object, ObjectController, Tag, TagMap}, tiled::{ InfiniteScrolledMap, PartialUpdateStatus, RegularBackgroundSize, TileFormat, TileSet, - TileSetting, VRamManager, + TileSetting, TiledMap, VRamManager, }, Priority, HEIGHT, WIDTH, }, fixnum::{FixedNum, Vector2D}, input::{self, Button, ButtonController}, + sound::mixer::Frequency, }; use alloc::boxed::Box; @@ -785,7 +786,7 @@ fn agb_main(mut gba: agb::Gba) -> ! { pub fn main(mut agb: agb::Gba) -> ! { 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 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.show(); - vram.set_background_palettes(tile_sheet::background.palettes); + vram.set_background_palettes(tile_sheet::PALETTES); let object = agb.display.object.get(); - let mut mixer = agb.mixer.mixer(); + let mut mixer = agb.mixer.mixer(Frequency::Hz10512); mixer.enable(); let mut music_box = sfx::MusicBox::new(); diff --git a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs index 64243b9b..7be40dda 100644 --- a/examples/the-hat-chooses-the-wizard/src/splash_screen.rs +++ b/examples/the-hat-chooses-the-wizard/src/splash_screen.rs @@ -1,6 +1,6 @@ use super::sfx::MusicBox; use agb::{ - display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, VRamManager}, + display::tiled::{RegularMap, TileFormat, TileSet, TileSetting, TiledMap, VRamManager}, sound::mixer::Mixer, }; @@ -18,21 +18,14 @@ pub fn show_splash_screen( map: &mut RegularMap, vram: &mut VRamManager, ) { - map.set_scroll_pos((0u16, 0u16).into()); - let (tileset, palette) = match which { - SplashScreen::Start => { - let tileset = TileSet::new(splash_screens::splash.tiles, TileFormat::FourBpp); + map.set_scroll_pos((0i16, 0i16).into()); + let tileset = match which { + SplashScreen::Start => TileSet::new(splash_screens::splash.tiles, TileFormat::FourBpp), - (tileset, splash_screens::splash.palettes) - } - SplashScreen::End => { - let tileset = TileSet::new( - splash_screens::thanks_for_playing.tiles, - TileFormat::FourBpp, - ); - - (tileset, splash_screens::thanks_for_playing.palettes) - } + SplashScreen::End => TileSet::new( + splash_screens::thanks_for_playing.tiles, + TileFormat::FourBpp, + ), }; let vblank = agb::interrupt::VBlank::get(); @@ -77,7 +70,7 @@ pub fn show_splash_screen( } map.commit(vram); - vram.set_background_palettes(palette); + vram.set_background_palettes(splash_screens::PALETTES); map.show(); loop { diff --git a/examples/the-purple-night/Cargo.toml b/examples/the-purple-night/Cargo.toml index 33f2e83d..eeac7e0f 100644 --- a/examples/the-purple-night/Cargo.toml +++ b/examples/the-purple-night/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [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 } [build-dependencies] diff --git a/examples/the-purple-night/gfx/background.toml b/examples/the-purple-night/gfx/background.toml index 0cb20336..c9a8ac22 100644 --- a/examples/the-purple-night/gfx/background.toml +++ b/examples/the-purple-night/gfx/background.toml @@ -1,6 +1,7 @@ version = "1.0" +transparent_colour = "53269a" + [image.background] filename = "background.png" tile_size = "8x8" -transparent_colour = "53269a" diff --git a/examples/the-purple-night/gfx/objects.toml b/examples/the-purple-night/gfx/objects.toml deleted file mode 100644 index 4f2626b5..00000000 --- a/examples/the-purple-night/gfx/objects.toml +++ /dev/null @@ -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" diff --git a/examples/the-purple-night/src/main.rs b/examples/the-purple-night/src/main.rs index 07fc59cc..922b4e3a 100644 --- a/examples/the-purple-night/src/main.rs +++ b/examples/the-purple-night/src/main.rs @@ -22,6 +22,7 @@ use agb::{ input::{Button, ButtonController, Tri}, interrupt::VBlank, rng, + sound::mixer::Frequency, }; use generational_arena::Arena; use sfx::Sfx; @@ -129,7 +130,7 @@ impl<'a> Level<'a> { let factor: Number = Number::new(1) / Number::new(8); 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())); } let position = tilemap::WIDTH as usize * y as usize + x as usize; @@ -1878,7 +1879,7 @@ enum MoveState { impl<'a> Game<'a> { fn has_just_reached_end(&self) -> bool { 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, } } @@ -1901,13 +1902,13 @@ impl<'a> Game<'a> { if self.has_just_reached_end() { 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.boss = BossState::Active(Boss::new(object_controller, self.offset)) } } MoveState::PinnedAtEnd => { - self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into(); + self.offset.x = (tilemap::WIDTH * 8 - 248).into(); } MoveState::FollowingPlayer => { 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); self.offset.x += difference / 8; - if self.offset.x > (tilemap::WIDTH as i32 * 8 - 248).into() { - self.offset.x = (tilemap::WIDTH as i32 * 8 - 248).into(); + if self.offset.x > (tilemap::WIDTH * 8 - 248).into() { + self.offset.x = (tilemap::WIDTH * 8 - 248).into(); } else if self.offset.x < 8.into() { self.offset.x = 8.into(); self.move_state = MoveState::Ending; @@ -2146,7 +2147,7 @@ impl<'a> Game<'a> { } 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 b = modified_palette.colour(1); @@ -2160,7 +2161,7 @@ impl<'a> Game<'a> { } 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); @@ -2205,7 +2206,7 @@ fn game_with_level(gba: &mut agb::Gba) { let vblank = agb::interrupt::VBlank::get(); vblank.wait_for_vblank(); - let mut mixer = gba.mixer.mixer(); + let mut mixer = gba.mixer.mixer(Frequency::Hz18157); mixer.enable(); let mut sfx = sfx::Sfx::new(&mut mixer); @@ -2216,7 +2217,7 @@ fn game_with_level(gba: &mut agb::Gba) { loop { 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);