agb 0.23.1

Library for Game Boy Advance Development
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
#![warn(missing_docs)]
use agb_fixnum::FixedWidthSignedInteger;
use alloc::rc::Rc;
use bilge::prelude::*;
use core::alloc::Layout;

use crate::{
    display::{
        GraphicsFrame, Priority,
        affine::AffineMatrix,
        tiled::{TileFormat, screenblock::Screenblock, tiles::Tiles},
    },
    fixnum::{Num, Vector2D},
};

use super::{
    AffineBackgroundCommitData, AffineBackgroundData, AffineBackgroundId,
    BackgroundControlRegister, DynamicTile256, SCREENBLOCK_SIZE, TRANSPARENT_TILE_INDEX, TileIndex,
    TileSet, VRAM_MANAGER,
};

/// The size of the affine background.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u16)]
pub enum AffineBackgroundSize {
    /// 16x16 tiles or 128x128px
    Background16x16 = 0,
    /// 32x32 tiles or 256x256px
    Background32x32 = 1,
    /// 64x64 tiles or 512x512px
    Background64x64 = 2,
    /// 128x128 tiles or 1024x1024px
    Background128x128 = 3,
}

/// Whether the background should wrap at the edges.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u16)]
pub enum AffineBackgroundWrapBehaviour {
    /// Don't wrap and instead show the default background colour.
    NoWrap = 0,
    /// Wrap at the edges similar to the behaviour for regular backgrounds.
    Wrap = 1,
}

impl AffineBackgroundSize {
    fn width(self) -> usize {
        match self {
            AffineBackgroundSize::Background16x16 => 16,
            AffineBackgroundSize::Background32x32 => 32,
            AffineBackgroundSize::Background64x64 => 64,
            AffineBackgroundSize::Background128x128 => 128,
        }
    }

    pub(crate) fn num_tiles(self) -> usize {
        self.width() * self.width()
    }

    pub(crate) fn layout(self) -> Layout {
        Layout::from_size_align(self.num_tiles(), SCREENBLOCK_SIZE)
            .expect("Failed to create layout, should never happen")
    }

    fn gba_offset(self, pos: Vector2D<i32>) -> usize {
        let x_mod = pos.x & (self.width() as i32 - 1);
        let y_mod = pos.y & (self.width() as i32 - 1);

        let pos = x_mod + (self.width() as i32 * y_mod);

        pos as usize
    }
}

/// Represents a collection of tiles ready to display as an affine background.
///
/// Affine backgrounds work slightly differently to regular backgrounds.
/// You can have at most 2 of them on display at once, and they can only use 256-colour tiles.
/// Also, no per-tile transformations are possible.
/// Finally, only 256 distinct tiles can be used at once across all affine backgrounds.
///
/// Note that while this is in scope, space in
/// the GBA's VRAM will be allocated and unavailable for other backgrounds. You should use the
/// smallest [`AffineBackgroundSize`] you can while still being able to render the scene you want.
///
/// You can show up to 2 affine backgrounds at once (or 1 affine background and 2 [regular backgrounds](super::RegularBackground)).
///
/// to display a given affine background to the screen, you need to call its [show()](AffineBackground::show()) method on
/// a given [`GraphicsFrame`](crate::display::GraphicsFrame).
///
/// ## Example
///
/// ```rust,no_run
/// # #![no_main]
/// # #![no_std]
/// #
/// # use agb::Gba;
/// # fn foo(gba: &mut Gba) {
/// use agb::display::{
///     Priority,
///     tiled::{AffineBackground, AffineBackgroundSize, AffineBackgroundWrapBehaviour, VRAM_MANAGER},
/// };
///
/// let mut gfx = gba.graphics.get();
///
/// let bg = AffineBackground::new(
///     Priority::P0,
///     AffineBackgroundSize::Background16x16,
///     AffineBackgroundWrapBehaviour::NoWrap,
/// );
///
/// // load the background with some tiles
///
/// loop {
///     let mut frame = gfx.frame();
///     bg.show(&mut frame);
///     frame.commit();
/// }
/// # }
/// ```
pub struct AffineBackground {
    priority: Priority,

    tiles: Tiles<u8>,
    screenblock: Rc<Screenblock<AffineBackgroundSize>>,

    scroll: Vector2D<Num<i32, 8>>,

    transform: AffineMatrixBackground,
    wrap_behaviour: AffineBackgroundWrapBehaviour,
}

impl AffineBackground {
    /// Create a new affine background with given `priority`, `size` and `warp_behaviour`.
    ///
    /// This allocates some space in VRAM to store the actual tile data, but doesn't show anything
    /// until you call the [`show()`](AffineBackground::show()) function on a [`GraphicsFrame`](crate::display::GraphicsFrame).
    ///
    /// You can have more `AffineBackground` instances then there are available backgrounds
    /// to show at once, but you can only show 2 at once in a given frame (or 1 and a up to 2
    /// [regular backgrounds](super::RegularBackground)).
    ///
    /// For [`Priority`], a higher priority is rendered first, so is behind lower priorities.
    /// Therefore, `P0` will be rendered at the _front_ and `P3` at the _back_. For equal priorities,
    /// backgrounds are rendered _behind_ objects.
    #[must_use]
    pub fn new(
        priority: Priority,
        size: AffineBackgroundSize,
        wrap_behaviour: AffineBackgroundWrapBehaviour,
    ) -> Self {
        Self {
            priority,

            tiles: Tiles::new(size.num_tiles(), TileFormat::EightBpp),

            scroll: Vector2D::default(),

            screenblock: Rc::new(Screenblock::new(size)),

            transform: AffineMatrixBackground::default(),
            wrap_behaviour,
        }
    }

    /// Set the current scroll position.
    ///
    /// Returns self so you can chain with other `set_` calls.
    pub fn set_scroll_pos(&mut self, scroll: impl Into<Vector2D<Num<i32, 8>>>) -> &mut Self {
        self.scroll = scroll.into();
        self
    }

    /// Get the current scroll position.
    #[must_use]
    pub fn scroll_pos(&self) -> Vector2D<Num<i32, 8>> {
        self.scroll
    }

    /// Set a tile at a given position to the given tile index.
    ///
    /// Because of limitations of the Game Boy Advance, this does not take a [`TileSetting`](super::TileSetting)
    /// but instead just a tile index.
    ///
    /// The `tileset` must also be a 256 colour background imported with the 256 option in
    /// [`include_background_gfx!`](crate::include_background_gfx!).
    ///
    /// This will resulting in copying the tile data to video RAM. However, setting the same tile across multiple locations
    /// in the background will reference that same tile only once to reduce video RAM usage.
    ///
    /// Returns self so you can chain with other `set_` calls.
    pub fn set_tile(
        &mut self,
        pos: impl Into<Vector2D<i32>>,
        tileset: &TileSet,
        tile_index: u16,
    ) -> &mut Self {
        assert_eq!(
            tileset.format(),
            TileFormat::EightBpp,
            "Can only use 256 colour tiles in an affine background"
        );

        let pos = self.screenblock.size().gba_offset(pos.into());
        self.set_tile_at_pos(pos, tileset, tile_index);

        self
    }

    /// Sets a tile at the given position to the given [`DynamicTile256`].
    ///
    /// Because of limitations of the Game Boy Advance, affine backgrounds do not support
    /// per-tile effects like flipping. Only 256-colour tiles are supported.
    ///
    /// Returns self so you can chain with other `set_` calls.
    pub fn set_tile_dynamic256(
        &mut self,
        pos: impl Into<Vector2D<i32>>,
        tile: &DynamicTile256,
    ) -> &mut Self {
        let pos = self.screenblock.size().gba_offset(pos.into());
        self.set_tile_at_pos(pos, &tile.tile_set(), tile.tile_id());

        self
    }

    /// Set the current transformation matrix.
    ///
    /// Returns self so you can chain with other `set_` calls.
    pub fn set_transform(&mut self, transform: impl Into<AffineMatrixBackground>) -> &mut Self {
        self.transform = transform.into();
        self
    }

    /// Get the current transformation matrix.
    #[must_use]
    pub fn transform(&self) -> AffineMatrixBackground {
        self.transform
    }

    /// Set the wrapping behaviour.
    ///
    /// Returns self so you can chain with other `set_` calls.
    pub fn set_wrap_behaviour(
        &mut self,
        wrap_behaviour: AffineBackgroundWrapBehaviour,
    ) -> &mut Self {
        self.wrap_behaviour = wrap_behaviour;
        self
    }

    /// Gets the wrapping behaviour.
    #[must_use]
    pub fn wrap_behaviour(&self) -> AffineBackgroundWrapBehaviour {
        self.wrap_behaviour
    }

    fn set_tile_at_pos(&mut self, pos: usize, tileset: &TileSet, tile_index: u16) -> &mut Self {
        let old_tile = self.tiles.get(pos);

        let new_tile = if tile_index != TRANSPARENT_TILE_INDEX {
            let new_tile_idx = VRAM_MANAGER.add_tile(tileset, tile_index, true);
            if new_tile_idx.raw_index() > u8::MAX as u16 {
                VRAM_MANAGER.remove_tile(new_tile_idx);
                0
            } else {
                new_tile_idx.raw_index() as u8
            }
        } else {
            0
        };

        if old_tile != 0 {
            VRAM_MANAGER.remove_tile(TileIndex::EightBpp(old_tile as u16));
        }

        if old_tile != new_tile {
            self.tiles.set_tile(pos, new_tile);
        }

        self
    }

    /// Show this background on the given frame.
    ///
    /// The background itself won't be visible until you call [`commit()`](GraphicsFrame::commit()) on the provided [`GraphicsFrame`].
    ///
    /// After this call, you can safely drop the background and the provided [`GraphicsFrame`] will maintain the
    /// references needed until the frame is drawn on screen.
    ///
    /// Note that after this call, any modifications made to the background will _not_ show this frame. Effectively
    /// calling `show()` takes a snapshot of the current state of the background, so you can even modify
    /// the background and `show()` it again and both will show in the frame.
    ///
    /// Returns an [`AffineBackgroundId`] which can be used if you want to apply any additional effects to the background
    /// such as applying [dma effects](crate::dma).
    pub fn show(&self, frame: &mut GraphicsFrame<'_>) -> AffineBackgroundId {
        let commit_data = if self.tiles.is_dirty(self.screenblock.ptr()) {
            Some(AffineBackgroundCommitData {
                tiles: self.tiles.clone(),
                screenblock: Rc::clone(&self.screenblock),
            })
        } else {
            None
        };

        frame.bg_frame.set_next_affine(AffineBackgroundData {
            bg_ctrl: self.bg_ctrl(),
            scroll_offset: self.scroll,
            affine_transform: self.transform,
            commit_data,
        })
    }

    fn bg_ctrl(&self) -> BackgroundControlRegister {
        let mut background_control_register = BackgroundControlRegister::default();

        background_control_register.set_priority(self.priority.into());
        background_control_register
            .set_screen_base_block(u5::new(self.screenblock.screen_base_block() as u8));
        background_control_register.set_overflow_behaviour(self.wrap_behaviour.into());
        background_control_register.set_screen_size(self.screenblock.size().into());

        background_control_register
    }

    /// Returns the size of the affine background.
    #[must_use]
    pub fn size(&self) -> AffineBackgroundSize {
        self.screenblock.size()
    }

    /// Set the current priority for the background.
    ///
    /// This won't take effect until the next time you call [`show()`](AffineBackground::show()).
    /// Returns self so you can chain with other `set_` calls.
    pub fn set_priority(&mut self, priority: Priority) -> &mut Self {
        self.priority = priority;
        self
    }

    /// Gets the current priority for the background.
    #[must_use]
    pub fn priority(&self) -> Priority {
        self.priority
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(C, packed(4))]
#[allow(missing_docs)]
/// An affine matrix that can be used in affine backgrounds
pub struct AffineMatrixBackground {
    pub a: Num<i16, 8>,
    pub b: Num<i16, 8>,
    pub c: Num<i16, 8>,
    pub d: Num<i16, 8>,
    pub x: Num<i32, 8>,
    pub y: Num<i32, 8>,
}

impl Default for AffineMatrixBackground {
    fn default() -> Self {
        Self::from_affine::<i16, 8>(AffineMatrix::identity())
    }
}

impl From<AffineMatrix> for AffineMatrixBackground {
    fn from(value: AffineMatrix) -> Self {
        Self::from_affine(value)
    }
}

impl AffineMatrixBackground {
    /// Converts the matrix to one which can be used in affine backgrounds
    /// wrapping any value which is too large to be represented there.
    #[must_use]
    pub fn from_affine<I, const N: usize>(affine: AffineMatrix<Num<I, N>>) -> Self
    where
        I: FixedWidthSignedInteger,
        i32: From<I>,
    {
        let a: Num<i32, 8> = affine.a.change_base();
        let b: Num<i32, 8> = affine.b.change_base();
        let c: Num<i32, 8> = affine.c.change_base();
        let d: Num<i32, 8> = affine.d.change_base();

        Self {
            a: Num::from_raw(a.to_raw() as i16),
            b: Num::from_raw(b.to_raw() as i16),
            c: Num::from_raw(c.to_raw() as i16),
            d: Num::from_raw(d.to_raw() as i16),
            x: affine.x.change_base(),
            y: affine.y.change_base(),
        }
    }

    #[must_use]
    /// Converts to the affine matrix that is usable in performing efficient
    /// calculations.
    pub fn to_affine_matrix(&self) -> AffineMatrix {
        AffineMatrix {
            a: self.a.change_base(),
            b: self.b.change_base(),
            c: self.c.change_base(),
            d: self.d.change_base(),
            x: self.x,
            y: self.y,
        }
    }

    #[must_use]
    /// Creates a transformation matrix using GBA specific syscalls.
    /// This can be done using the standard transformation matrices like
    ///
    /// ```rust,no_run
    /// # #![no_std]
    /// # #![no_main]
    /// # use agb_fixnum::{Vector2D, Num};
    /// use agb::display::AffineMatrix;
    /// # fn from_scale_rotation_position(
    /// #     transform_origin: Vector2D<Num<i32, 8>>,
    /// #     scale: Vector2D<Num<i32, 8>>,
    /// #     rotation: Num<i32, 8>,
    /// #     position: Vector2D<Num<i32, 8>>,
    /// # ) {
    /// let A = AffineMatrix::from_translation(-transform_origin)
    ///     * AffineMatrix::from_scale(scale)
    ///     * AffineMatrix::from_rotation(rotation)
    ///     * AffineMatrix::from_translation(position);
    /// # }
    /// ```
    pub fn from_scale_rotation_position(
        transform_origin: impl Into<Vector2D<Num<i32, 8>>>,
        scale: impl Into<Vector2D<Num<i32, 8>>>,
        rotation: Num<i32, 16>,
        position: impl Into<Vector2D<i16>>,
    ) -> Self {
        crate::syscall::bg_affine_matrix(
            transform_origin.into(),
            position.into(),
            scale.into().try_change_base().unwrap(),
            rotation.rem_euclid(1.into()).try_change_base().unwrap(),
        )
    }
}

impl From<AffineMatrixBackground> for AffineMatrix {
    fn from(mat: AffineMatrixBackground) -> Self {
        mat.to_affine_matrix()
    }
}

#[cfg(test)]
mod test;