#![warn(missing_docs)]
use core::{fmt::Debug, ptr::NonNull};
use alloc::{slice, vec::Vec};
use tile_allocator::TileAllocator;
mod tile_allocator;
use crate::{
display::{Palette16, Rgb15},
dma,
hash_map::{Entry, HashMap},
memory_mapped::MemoryMapped1DArray,
util::SyncUnsafeCell,
};
use super::VRAM_START;
const PALETTE_BACKGROUND: MemoryMapped1DArray<Rgb15, 256> =
unsafe { MemoryMapped1DArray::new(0x0500_0000) };
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TileFormat {
FourBpp = 5,
EightBpp = 6,
}
impl TileFormat {
pub(crate) const fn tile_size(self) -> usize {
1 << self as usize
}
}
pub struct TileSet {
tiles: &'static [u8],
format: TileFormat,
}
impl TileSet {
#[must_use]
pub const unsafe fn new(tiles: &'static [u8], format: TileFormat) -> Self {
assert!(
tiles.len().is_multiple_of(format.tile_size()),
"The length of `tiles` must be a multiple of `format.tile_size()`"
);
Self { tiles, format }
}
#[must_use]
pub const fn format(&self) -> TileFormat {
self.format
}
#[must_use]
pub fn get_tile_data(&self, tile_id: u16) -> &'static [u32] {
assert!(
tile_id as usize * self.format.tile_size() < self.tiles.len(),
"{tile_id} is too big for this tileset ({} tiles)",
self.tiles.len() / self.format.tile_size()
);
let tile_id = tile_id as usize;
let tile_size = self.format.tile_size();
unsafe {
slice::from_raw_parts(
self.tiles.as_ptr().add(tile_id * tile_size).cast(),
tile_size / 4,
)
}
}
fn reference(&self) -> NonNull<[u8]> {
self.tiles.into()
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum TileIndex {
FourBpp(u16),
EightBpp(u16),
}
impl TileIndex {
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 u16),
}
}
pub(crate) const fn raw_index(self) -> u16 {
match self {
TileIndex::FourBpp(x) => x,
TileIndex::EightBpp(x) => x,
}
}
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,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct TileReference(NonNull<u32>);
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct TileInTileSetReference {
tileset: *const [u8],
tile: u16,
is_affine: bool,
}
impl TileInTileSetReference {
fn new(tileset: *const [u8], tile: u16, is_affine: bool) -> Self {
Self {
tileset,
tile,
is_affine,
}
}
}
#[derive(Clone, Default)]
struct TileReferenceCount {
reference_count: u16,
tile_in_tile_set: Option<TileInTileSetReference>,
}
impl TileReferenceCount {
fn new(tile_in_tile_set: TileInTileSetReference) -> Self {
Self {
reference_count: 1,
tile_in_tile_set: Some(tile_in_tile_set),
}
}
fn increment_reference_count(&mut self) {
debug_assert!(self.tile_in_tile_set.is_some());
self.reference_count += 1;
}
fn decrement_reference_count(&mut self) -> u16 {
assert!(
self.reference_count > 0,
"Trying to decrease the reference count below 0",
);
debug_assert!(self.tile_in_tile_set.is_some());
self.reference_count -= 1;
self.reference_count
}
fn clear(&mut self) {
self.reference_count = 0;
self.tile_in_tile_set = None;
}
fn current_count(&self) -> u16 {
self.reference_count
}
}
#[non_exhaustive]
pub struct DynamicTile16 {
tile_data: &'static mut [u32],
}
impl Debug for DynamicTile16 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::write!(f, "DynamicTile16({})", self.tile_id())
}
}
impl DynamicTile16 {
#[must_use]
pub fn new() -> Self {
VRAM_MANAGER.new_dynamic_tile()
}
#[must_use]
pub fn fill_with(self, colour_index: u8) -> Self {
let colour_index = u32::from(colour_index);
let mut value = 0;
for i in 0..8 {
value |= colour_index << (i * 4);
}
self.tile_data.fill(value);
self
}
#[must_use]
pub fn data_mut(&mut self) -> &mut [u32] {
self.tile_data
}
#[must_use]
pub fn data(&self) -> &[u32] {
self.tile_data
}
#[must_use]
pub(crate) fn tile_set(&self) -> TileSet {
let tiles = unsafe {
slice::from_raw_parts_mut(
VRAM_START as *mut u8,
1024 * TileFormat::FourBpp.tile_size(),
)
};
unsafe { TileSet::new(tiles, TileFormat::FourBpp) }
}
#[must_use]
pub(crate) fn tile_id(&self) -> u16 {
let difference = self.tile_data.as_ptr() as usize - VRAM_START;
(difference / TileFormat::FourBpp.tile_size()) as u16
}
pub fn set_pixel(&mut self, x: usize, y: usize, palette_index: u8) {
assert!((0..9).contains(&x));
assert!((0..9).contains(&y));
assert!(palette_index < 16);
let index = x + y * 8;
let word_index = index / 8;
let nibble_offset = index % 8;
let ptr = &mut self.tile_data[word_index] as *mut u32;
let mask = 0xf << (nibble_offset * 4);
let palette_value = u32::from(palette_index) << (nibble_offset * 4);
unsafe {
let current_value = ptr.read_volatile();
ptr.write_volatile((current_value & !mask) | palette_value);
}
}
}
impl Default for DynamicTile16 {
fn default() -> Self {
Self::new()
}
}
impl Drop for DynamicTile16 {
fn drop(&mut self) {
unsafe {
VRAM_MANAGER.drop_dynamic_tile_16(self);
}
}
}
#[non_exhaustive]
pub struct DynamicTile256 {
tile_data: &'static mut [u32],
}
impl Debug for DynamicTile256 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::write!(f, "DynamicTile256({})", self.tile_id())
}
}
impl DynamicTile256 {
#[must_use]
pub fn new() -> Self {
VRAM_MANAGER.new_dynamic_tile_256(false)
}
#[must_use]
pub fn new_affine() -> Self {
VRAM_MANAGER.new_dynamic_tile_256(true)
}
#[must_use]
pub fn fill_with(self, colour_index: u8) -> Self {
let colour_index = u32::from(colour_index);
let value =
colour_index | (colour_index << 8) | (colour_index << 16) | (colour_index << 24);
self.tile_data.fill(value);
self
}
#[must_use]
pub fn data_mut(&mut self) -> &mut [u32] {
self.tile_data
}
#[must_use]
pub fn data(&self) -> &[u32] {
self.tile_data
}
#[must_use]
pub(crate) fn tile_set(&self) -> TileSet {
let tiles = unsafe {
slice::from_raw_parts_mut(
VRAM_START as *mut u8,
1024 * TileFormat::EightBpp.tile_size(),
)
};
unsafe { TileSet::new(tiles, TileFormat::EightBpp) }
}
#[must_use]
pub(crate) fn tile_id(&self) -> u16 {
let difference = self.tile_data.as_ptr() as usize - VRAM_START;
(difference / TileFormat::EightBpp.tile_size()) as u16
}
pub fn set_pixel(&mut self, x: usize, y: usize, palette_index: u8) {
assert!((0..8).contains(&x));
assert!((0..8).contains(&y));
let index = x + y * 8;
let word_index = index / 4;
let byte_offset = index % 4;
let ptr = &mut self.tile_data[word_index] as *mut u32;
let mask = 0xff << (byte_offset * 8);
let palette_value = u32::from(palette_index) << (byte_offset * 8);
unsafe {
let current_value = ptr.read_volatile();
ptr.write_volatile((current_value & !mask) | palette_value);
}
}
}
impl Default for DynamicTile256 {
fn default() -> Self {
Self::new()
}
}
impl Drop for DynamicTile256 {
fn drop(&mut self) {
unsafe {
VRAM_MANAGER.drop_dynamic_tile_256(self);
}
}
}
pub struct VRamManager {
inner: SyncUnsafeCell<VRamManagerInner>,
}
pub static VRAM_MANAGER: VRamManager = unsafe { VRamManager::new() };
impl VRamManager {
const unsafe fn new() -> Self {
Self {
inner: SyncUnsafeCell::new(unsafe { VRamManagerInner::new() }),
}
}
pub(crate) unsafe fn init(&self) {
self.with(|inner| unsafe {
inner.init();
});
}
fn with<T>(&self, f: impl FnOnce(&mut VRamManagerInner) -> T) -> T {
f(unsafe { &mut *self.inner.get() })
}
}
impl VRamManager {
unsafe fn drop_dynamic_tile_16(&self, tile: &DynamicTile16) {
self.with(|inner| unsafe { inner.remove_dynamic_tile_16(tile) });
}
unsafe fn drop_dynamic_tile_256(&self, tile: &DynamicTile256) {
self.with(|inner| unsafe { inner.remove_dynamic_tile_256(tile) });
}
pub(crate) fn new_dynamic_tile(&self) -> DynamicTile16 {
self.with(VRamManagerInner::new_dynamic_tile_16)
}
pub(crate) fn new_dynamic_tile_256(&self, is_affine: bool) -> DynamicTile256 {
self.with(|inner| inner.new_dynamic_tile_256(is_affine))
}
pub(crate) fn remove_tile(&self, index: TileIndex) {
self.with(|inner| inner.remove_tile(index));
}
pub(crate) fn increase_reference(&self, index: TileIndex) {
self.with(|inner| inner.increase_reference(index));
}
pub(crate) fn add_tile(
&self,
tile_set: &TileSet,
tile_index: u16,
is_affine: bool,
) -> TileIndex {
self.with(|inner| inner.add_tile(tile_set, tile_index, is_affine))
}
pub(crate) fn gc(&self) {
self.with(VRamManagerInner::gc);
}
pub fn set_background_palette(&self, pal_index: u8, palette: &Palette16) {
self.with(|inner| inner.set_background_palette(pal_index, palette));
}
pub fn set_background_palettes(&self, palettes: &[Palette16]) {
self.with(|inner| inner.set_background_palettes(palettes));
}
pub fn replace_tile(
&self,
source_tile_set: &TileSet,
source_tile: u16,
target_tile_set: &TileSet,
target_tile: u16,
) {
self.with(|inner| {
inner.replace_tile(
source_tile_set,
source_tile,
target_tile_set,
target_tile,
false,
);
});
}
pub fn replace_tile_affine(
&self,
source_tile_set: &TileSet,
source_tile: u16,
target_tile_set: &TileSet,
target_tile: u16,
) {
self.with(|inner| {
inner.replace_tile(
source_tile_set,
source_tile,
target_tile_set,
target_tile,
true,
);
});
}
#[must_use]
pub fn background_palette_colour_dma(
&self,
pal_index: usize,
colour_index: usize,
) -> dma::DmaControllable<Rgb15> {
self.with(|inner| inner.background_palette_colour_dma(pal_index, colour_index))
}
#[must_use]
pub fn background_palette_colour_256_dma(
&self,
colour_index: usize,
) -> dma::DmaControllable<Rgb15> {
assert!(colour_index < 256);
self.background_palette_colour_dma(colour_index / 16, colour_index % 16)
}
pub fn set_background_palette_colour(
&self,
pal_index: usize,
colour_index: usize,
colour: Rgb15,
) {
self.with(|inner| inner.set_background_palette_colour(pal_index, colour_index, colour));
}
pub fn set_background_palette_colour_256(&self, colour_index: usize, colour: Rgb15) {
assert!(colour_index < 256);
self.set_background_palette_colour(colour_index / 16, colour_index % 16, colour);
}
#[must_use]
pub fn find_colour_index_16(&self, palette_index: usize, colour: Rgb15) -> Option<usize> {
self.with(|inner| inner.find_colour_index_16(palette_index, colour))
}
#[must_use]
pub fn find_colour_index_256(&self, colour: Rgb15) -> Option<usize> {
self.with(|inner| inner.find_colour_index_256(colour))
}
}
struct VRamManagerInner {
tile_set_to_vram: HashMap<TileInTileSetReference, TileReference>,
reference_counts: Vec<TileReferenceCount>,
tile_allocator: TileAllocator,
indices_to_gc: Vec<TileIndex>,
}
impl VRamManagerInner {
const unsafe fn new() -> Self {
let tile_set_to_vram: HashMap<TileInTileSetReference, TileReference> = HashMap::new();
Self {
tile_set_to_vram,
reference_counts: Vec::new(),
indices_to_gc: Vec::new(),
tile_allocator: unsafe { TileAllocator::new() },
}
}
unsafe fn init(&mut self) {
unsafe {
self.tile_allocator.init();
}
}
fn index_from_reference(reference: TileReference, format: TileFormat) -> TileIndex {
let difference = reference.0.as_ptr() as usize - VRAM_START;
TileIndex::new(difference / format.tile_size(), format)
}
fn reference_from_index(index: TileIndex) -> TileReference {
let ptr = (index.raw_index() as usize * index.format().tile_size()) + VRAM_START;
TileReference(NonNull::new(ptr as *mut _).unwrap())
}
#[must_use]
fn new_dynamic_tile_16(&mut self) -> DynamicTile16 {
let tile_format = TileFormat::FourBpp;
let new_reference: NonNull<u32> = self.tile_allocator.alloc_for_regular(tile_format);
let tile_reference = TileReference(new_reference);
let index = Self::index_from_reference(tile_reference, tile_format);
let key = index.refcount_key();
let tiles = unsafe {
slice::from_raw_parts_mut(VRAM_START as *mut u8, 1024 * tile_format.tile_size())
};
let tileset_reference = TileInTileSetReference::new(tiles, index.raw_index(), false);
self.tile_set_to_vram
.insert(tileset_reference, tile_reference);
self.reference_counts
.resize(self.reference_counts.len().max(key + 1), Default::default());
self.reference_counts[key] = TileReferenceCount::new(tileset_reference);
DynamicTile16 {
tile_data: unsafe {
slice::from_raw_parts_mut(
tiles
.as_mut_ptr()
.add(index.raw_index() as usize * tile_format.tile_size())
.cast(),
tile_format.tile_size() / core::mem::size_of::<u32>(),
)
},
}
}
#[must_use]
fn new_dynamic_tile_256(&mut self, is_affine: bool) -> DynamicTile256 {
let tile_format = TileFormat::EightBpp;
let new_reference = if is_affine {
self.tile_allocator.alloc_for_affine()
} else {
self.tile_allocator.alloc_for_regular(tile_format)
};
let tile_reference = TileReference(new_reference);
let index = Self::index_from_reference(tile_reference, tile_format);
let key = index.refcount_key();
let tiles = unsafe {
slice::from_raw_parts_mut(VRAM_START as *mut u8, 1024 * tile_format.tile_size())
};
let tileset_reference = TileInTileSetReference::new(tiles, index.raw_index(), true);
self.tile_set_to_vram
.insert(tileset_reference, tile_reference);
self.reference_counts
.resize(self.reference_counts.len().max(key + 1), Default::default());
self.reference_counts[key] = TileReferenceCount::new(tileset_reference);
DynamicTile256 {
tile_data: unsafe {
slice::from_raw_parts_mut(
tiles
.as_mut_ptr()
.add(index.raw_index() as usize * tile_format.tile_size())
.cast(),
tile_format.tile_size() / core::mem::size_of::<u32>(),
)
},
}
}
unsafe fn remove_dynamic_tile_16(&mut self, dynamic_tile: &DynamicTile16) {
let pointer = NonNull::new(dynamic_tile.tile_data.as_ptr() as *mut _).unwrap();
let tile_reference = TileReference(pointer);
let tile_index = Self::index_from_reference(tile_reference, TileFormat::FourBpp);
self.remove_tile(tile_index);
}
unsafe fn remove_dynamic_tile_256(&mut self, dynamic_tile: &DynamicTile256) {
let pointer = NonNull::new(dynamic_tile.tile_data.as_ptr() as *mut _).unwrap();
let tile_reference = TileReference(pointer);
let tile_index = Self::index_from_reference(tile_reference, TileFormat::EightBpp);
self.remove_tile(tile_index);
}
#[inline(never)]
fn add_tile(&mut self, tile_set: &TileSet, tile: u16, is_affine: bool) -> TileIndex {
let tileset_reference =
TileInTileSetReference::new(tile_set.reference().as_ptr(), tile, is_affine);
let reference = self.tile_set_to_vram.entry(tileset_reference);
if let Entry::Occupied(reference) = reference {
let tile_index = Self::index_from_reference(*reference.get(), tile_set.format);
self.increase_reference(tile_index);
return tile_index;
}
let new_reference: NonNull<u32> = if is_affine {
self.tile_allocator.alloc_for_affine()
} else {
self.tile_allocator.alloc_for_regular(tile_set.format)
};
let tile_reference = TileReference(new_reference);
reference.or_insert(tile_reference);
self.copy_tile_to_location(tile_set, tile, tile_reference);
let index = Self::index_from_reference(tile_reference, tile_set.format);
let key = index.refcount_key();
self.reference_counts
.resize(self.reference_counts.len().max(key + 1), Default::default());
self.reference_counts[key] = TileReferenceCount::new(tileset_reference);
index
}
fn remove_tile(&mut self, tile_index: TileIndex) {
let key = tile_index.refcount_key();
let new_reference_count = self.reference_counts[key].decrement_reference_count();
if new_reference_count != 0 {
return;
}
self.indices_to_gc.push(tile_index);
}
fn increase_reference(&mut self, tile_index: TileIndex) {
let key = tile_index.refcount_key();
self.reference_counts[key].increment_reference_count();
}
fn gc(&mut self) {
for tile_index in self.indices_to_gc.drain(..) {
let key = tile_index.refcount_key();
if self.reference_counts[key].current_count() > 0 {
continue; }
let Some(tile_ref) = self.reference_counts[key].tile_in_tile_set.as_ref() else {
continue;
};
let tile_reference = Self::reference_from_index(tile_index);
unsafe {
self.tile_allocator
.dealloc(tile_reference.0, tile_index.format());
}
self.tile_set_to_vram.remove(tile_ref);
self.reference_counts[key].clear();
}
}
fn replace_tile(
&mut self,
source_tile_set: &TileSet,
source_tile: u16,
target_tile_set: &TileSet,
target_tile: u16,
is_affine: bool,
) {
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.reference().as_ptr(),
source_tile,
is_affine,
)) {
self.copy_tile_to_location(target_tile_set, target_tile, reference);
}
}
fn copy_tile_to_location(
&self,
tile_set: &TileSet,
tile_id: u16,
tile_reference: TileReference,
) {
let tile_format = tile_set.format;
let tile_size = tile_format.tile_size();
let tile_offset = (tile_id as usize) * tile_size;
let tile_data_start = unsafe { tile_set.tiles.as_ptr().add(tile_offset) };
let target_location = tile_reference.0.as_ptr() as *mut _;
unsafe {
match tile_format {
TileFormat::FourBpp => core::arch::asm!(
".rept 2",
"ldmia {src}!, {{{tmp1},{tmp2},{tmp3},{tmp4}}}",
"stmia {dest}!, {{{tmp1},{tmp2},{tmp3},{tmp4}}}",
".endr",
src = inout(reg) tile_data_start => _,
dest = inout(reg) target_location => _,
tmp1 = out(reg) _,
tmp2 = out(reg) _,
tmp3 = out(reg) _,
tmp4 = out(reg) _,
),
TileFormat::EightBpp => core::arch::asm!(
".rept 4",
"ldmia {src}!, {{{tmp1},{tmp2},{tmp3},{tmp4}}}",
"stmia {dest}!, {{{tmp1},{tmp2},{tmp3},{tmp4}}}",
".endr",
src = inout(reg) tile_data_start => _,
dest = inout(reg) target_location => _,
tmp1 = out(reg) _,
tmp2 = out(reg) _,
tmp3 = out(reg) _,
tmp4 = out(reg) _,
),
}
}
}
fn set_background_palette(&mut self, pal_index: u8, palette: &Palette16) {
assert!(pal_index < 16);
for (colour_index, &colour) in palette.colours.iter().enumerate() {
PALETTE_BACKGROUND.set(colour_index + 16 * pal_index as usize, colour);
}
}
#[must_use]
fn background_palette_colour_dma(
&self,
pal_index: usize,
colour_index: usize,
) -> dma::DmaControllable<Rgb15> {
assert!(pal_index < 16);
assert!(colour_index < 16);
unsafe {
dma::DmaControllable::new(
PALETTE_BACKGROUND
.as_ptr()
.add(16 * pal_index + colour_index),
)
}
}
fn set_background_palette_colour(
&mut self,
pal_index: usize,
colour_index: usize,
colour: Rgb15,
) {
assert!(pal_index < 16);
assert!(colour_index < 16);
PALETTE_BACKGROUND.set(colour_index + 16 * pal_index, colour);
}
fn set_background_palettes(&mut self, palettes: &[Palette16]) {
for (palette_index, entry) in palettes.iter().enumerate() {
self.set_background_palette(palette_index as u8, entry);
}
}
#[must_use]
fn find_colour_index_16(&self, palette_index: usize, colour: Rgb15) -> Option<usize> {
assert!(palette_index < 16);
(0..16).find(|i| PALETTE_BACKGROUND.get(palette_index * 16 + i) == colour)
}
#[must_use]
fn find_colour_index_256(&self, colour: Rgb15) -> Option<usize> {
(0..256).find(|&i| PALETTE_BACKGROUND.get(i) == colour)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Gba;
#[test_case]
fn can_create_dynamic_tile_16(_: &mut Gba) {
let tile = DynamicTile16::new();
assert_eq!(tile.data().len(), 8);
}
#[test_case]
fn can_create_dynamic_tile_256(_: &mut Gba) {
let tile = DynamicTile256::new();
assert_eq!(tile.data().len(), 16);
}
#[test_case]
fn dynamic_tile_16_fill_with(_: &mut Gba) {
let tile = DynamicTile16::new().fill_with(5);
for &word in tile.data() {
assert_eq!(word, 0x55555555);
}
}
#[test_case]
fn dynamic_tile_256_fill_with(_: &mut Gba) {
let tile = DynamicTile256::new().fill_with(0xAB);
for &word in tile.data() {
assert_eq!(word, 0xABABABAB);
}
}
#[test_case]
fn dynamic_tile_16_set_pixel(_: &mut Gba) {
let mut tile = DynamicTile16::new().fill_with(0);
tile.set_pixel(0, 0, 7);
assert_eq!(tile.data()[0] & 0xF, 7);
tile.set_pixel(3, 0, 10);
assert_eq!((tile.data()[0] >> 12) & 0xF, 10);
tile.set_pixel(0, 1, 15);
assert_eq!(tile.data()[1] & 0xF, 15);
}
#[test_case]
fn dynamic_tile_256_set_pixel(_: &mut Gba) {
let mut tile = DynamicTile256::new().fill_with(0);
tile.set_pixel(0, 0, 128);
assert_eq!(tile.data()[0] & 0xFF, 128);
tile.set_pixel(3, 0, 200);
assert_eq!((tile.data()[0] >> 24) & 0xFF, 200);
tile.set_pixel(0, 1, 255);
assert_eq!(tile.data()[2] & 0xFF, 255);
}
#[test_case]
fn can_create_and_drop_multiple_dynamic_tiles_16(_: &mut Gba) {
for _ in 0..10 {
let _tiles: Vec<_> = (0..50).map(|_| DynamicTile16::new()).collect();
}
VRAM_MANAGER.gc();
}
#[test_case]
fn can_create_and_drop_multiple_dynamic_tiles_256(_: &mut Gba) {
for _ in 0..10 {
let _tiles: Vec<_> = (0..20).map(|_| DynamicTile256::new()).collect();
}
VRAM_MANAGER.gc();
}
#[test_case]
fn dynamic_tile_256_data_mut(_: &mut Gba) {
let mut tile = DynamicTile256::new();
let data = tile.data_mut();
data[0] = 0x12345678;
data[15] = 0xDEADBEEF;
assert_eq!(tile.data()[0], 0x12345678);
assert_eq!(tile.data()[15], 0xDEADBEEF);
}
}