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
//! Module to control the GBA's screen.
//!
//! # Video Basics
//!
//! To configure the screen's display, you should first decide on the
//! [`DisplayControl`] value that you want to set to the
//! [`DISPCNT`](crate::mmio::DISPCNT) register. This configures several things,
//! but most importantly it determines the [`VideoMode`] for the display to use.
//!
//! The GBA has four Background layers. Depending on the current video mode,
//! different background layers will be available for use in either "text",
//! "affine", or "bitmap" mode.
//!
//! In addition to the background layers, there's also an "OBJ" layer. This
//! allows the display of a number of "objects", which can move independently of
//! any background. Generally, one or more objects will be used to display the
//! "sprites" within a game. Because there isn't an exact 1:1 mapping between
//! sprites and objects, these docs will attempt to only talk about objects.
//!
//! ## Color, Bit Depth, and Palettes
//!
//! [Color] values on the GBA are 5-bits-per-channel RGB values. They're always
//! bit-packed and aligned to 2, so think of them as being like a `u16`.
//!
//! Because of the GBA's limited memory, most images don't use direct color (one
//! color per pixel). Instead they use indexed color (one *palette index* per
//! pixel). Indexed image data can be 4-bits-per-pixel (4bpp) or
//! 8-bits-per-pixel (8bpp). In either case, the color values themselves are
//! stored in the PALRAM region. The PALRAM contains the [`BG_PALETTE`] and
//! [`OBJ_PALETTE`], which hold the color values for backgrounds and objects
//! respectively. Both palettes have 256 slots. The palettes are always indexed
//! with 8 bits total, but *how* those bits are determined depends on the bit
//! depth of the image:
//! * Things drawing with 8bpp image data index into the full range of the
//!   palette directly.
//! * Things drawing with 4bpp image data will also have a "palbank" setting.
//!   The palbank acts as the upper 4 bits of the index, selecting which block
//!   of 16 palette entries the that thing will be able to use. Then each 4-bit
//!   pixel within the image indexes within the palbank.
//!
//! In both 8bpp and 4bpp modes, if a particular pixel's index value is 0 then
//! that pixel is instead considered transparent. So 8bpp images can use 255
//! colors (+ transparent), and 4bpp images can use 15 colors (+ transparent).
//! Each background layer and each object can individually be set to display
//! with either 4bpp or 8bpp mode.
//!
//! ## Tiles, Screenblocks, and Charblocks
//!
//! The basic unit of the GBA's hardware graphics support is a "tile".
//! Regardless of their bit depth, a tile is always an 8x8 area. This means that
//! they're either 32 bytes (4bpp) or 64 bytes (8bpp). Since VRAM starts aligned
//! to 4, and since both size tiles are a multiple of 4 bytes in size, we model
//! tile data as being arrays of `u32` rather than arrays of `u8`. Having the
//! data stay aligned to 4 within the ROM gives a significant speed gain when
//! copying tiles from ROM into VRAM.
//!
//! The layout of tiles within a background is defined by a "screenblock".
//! * Text backgrounds use a fixed 32x32 size screenblock, with larger
//!   backgrounds using more than one screenblock. Each [TextEntry] value in the
//!   screenblock has a tile index (10-bit), bits for horizontal flip and
//!   vertical flip, and a palbank value. If the background is not in 4bpp mode
//!   the palbank value is simply ignored.
//! * Affine backgrounds always have a single screenblock each, and the size of
//!   the screenblock itself changes with the background's size (from 16x16 to
//!   128x128, in powers of 2). Each entry in an affine screenblock is just a
//!   `u8` tile index, with no special options. Affine backgrounds can't use
//!   4bpp color, and they also can't flip tiles on a per-tile basis.
//!
//! A background's screenblock is selected by an index (5-bit). The indexes go
//! in 2,048 byte (2k) jumps. This is exactly the size of a text screenblock,
//! but doesn't precisely match the size of any of the affine screenblocks.
//!
//! Because tile indexes can only be so large, there are also "charblocks". This
//! offsets all of the tile index values that the background uses, allowing you
//! to make better use of all of the VRAM. The charblock value provides a 16,384
//! byte (16k) offset, and can be in the range `0..=3`.
//!
//! ## Priority
//!
//! When more than one thing would be drawn to the same pixel, there's a
//! priority system that determines which pixel is actually drawn.
//! * Priority values are always 2-bit, the range `0..=3`. The priority acts
//!   like the sorting index, or you could also think of it as the distance from
//!   the viewer. Things with a *lower* priority number are *closer* to the
//!   viewer, and so they'll be what's drawn.
//! * Objects always draw over top a same-priority background.
//! * Lower indexed objects get drawn when two objects have the same priority.
//! * Lower numbered backgrounds get drawn when two backgrounds have the same
//!   priority.
//!
//! There's also one hardware bug that can occur: when there's two objects and
//! their the priority and index wouldn't sort them the same (eg: a lower index
//! number object has a higher priority number), if a background is *also*
//! between the two objects, then the object that's supposed to be behind the
//! background will instead appear through the background where the two objects
//! overlap. This might never happen to you, but if it does, the "fix" is to
//! sort your object entries so that any lower priority objects are also the
//! lower index objects.

use crate::macros::{
  pub_const_fn_new_zeroed, u16_bool_field, u16_enum_field, u16_int_field,
};
#[allow(unused_imports)]
use crate::prelude::*;

pub mod obj;

/// An RGB555 color value (packed into `u16`).
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Color(pub u16);
#[allow(clippy::unusual_byte_groupings)]
#[allow(missing_docs)]
impl Color {
  pub const BLACK: Color = Color(0b0_00000_00000_00000);
  pub const RED: Color = Color(0b0_00000_00000_11111);
  pub const GREEN: Color = Color(0b0_00000_11111_00000);
  pub const YELLOW: Color = Color(0b0_00000_11111_11111);
  pub const BLUE: Color = Color(0b0_11111_00000_00000);
  pub const MAGENTA: Color = Color(0b0_11111_00000_11111);
  pub const CYAN: Color = Color(0b0_11111_11111_00000);
  pub const WHITE: Color = Color(0b0_11111_11111_11111);

  pub_const_fn_new_zeroed!();
  u16_int_field!(0 - 4, red, with_red);
  u16_int_field!(5 - 9, green, with_green);
  u16_int_field!(10 - 14, blue, with_blue);

  /// Constructs a new color value from the given channel values.
  #[inline]
  #[must_use]
  pub const fn from_rgb(r: u16, g: u16, b: u16) -> Self {
    Self(r & 0b11111 | (g & 0b11111) << 5 | (b & 0b11111) << 10)
  }
}

/// The video mode controls how each background layer will operate.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u16)]
pub enum VideoMode {
  /// All four background layers use text mode.
  #[default]
  _0 = 0,
  /// BG0 and BG1 are text mode, while BG2 is affine. BG3 is unavailable.
  _1 = 1,
  /// BG2 and BG3 are affine. BG0 and BG1 are unavailable.
  _2 = 2,
  /// BG2 is a single full color bitmap.
  _3 = 3,
  /// BG2 holds two 8bpp indexmaps, and you can flip between.
  _4 = 4,
  /// BG2 holds two full color bitmaps of reduced size (only 160x128), and you
  /// can flip between.
  _5 = 5,
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct DisplayControl(u16);
impl DisplayControl {
  pub_const_fn_new_zeroed!();
  u16_enum_field!(0 - 2: VideoMode, video_mode, with_video_mode);
  u16_bool_field!(4, show_frame1, with_show_frame1);
  u16_bool_field!(5, hblank_oam_free, with_hblank_oam_free);
  u16_bool_field!(6, obj_vram_1d, with_obj_vram_1d);
  u16_bool_field!(7, forced_blank, with_forced_blank);
  u16_bool_field!(8, show_bg0, with_show_bg0);
  u16_bool_field!(9, show_bg1, with_show_bg1);
  u16_bool_field!(10, show_bg2, with_show_bg2);
  u16_bool_field!(11, show_bg3, with_show_bg3);
  u16_bool_field!(12, show_obj, with_show_obj);
  u16_bool_field!(13, enable_win0, with_enable_win0);
  u16_bool_field!(14, enable_win1, with_enable_win1);
  u16_bool_field!(15, enable_obj_win, with_enable_obj_win);
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct DisplayStatus(u16);
impl DisplayStatus {
  pub_const_fn_new_zeroed!();
  u16_bool_field!(0, currently_vblank, with_currently_vblank);
  u16_bool_field!(1, currently_hblank, with_currently_hblank);
  u16_bool_field!(2, currently_vcount, with_currently_vcount);
  u16_bool_field!(3, irq_vblank, with_irq_vblank);
  u16_bool_field!(4, irq_hblank, with_irq_hblank);
  u16_bool_field!(5, irq_vcount, with_irq_vcount);
  u16_int_field!(8 - 15, vcount_setting, with_vcount_setting);
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct BackgroundControl(u16);
impl BackgroundControl {
  pub_const_fn_new_zeroed!();
  u16_int_field!(0 - 1, priority, with_priority);
  u16_int_field!(2 - 3, charblock, with_charblock);
  u16_bool_field!(6, mosaic, with_mosaic);
  u16_bool_field!(7, bpp8, with_bpp8);
  u16_int_field!(8 - 12, screenblock, with_screenblock);
  u16_bool_field!(13, is_affine_wrapping, with_is_affine_wrapping);
  u16_int_field!(14 - 15, size, with_size);
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct WindowInside(u16);
impl WindowInside {
  pub_const_fn_new_zeroed!();
  u16_bool_field!(0, win0_bg0, with_win0_bg0);
  u16_bool_field!(1, win0_bg1, with_win0_bg1);
  u16_bool_field!(2, win0_bg2, with_win0_bg2);
  u16_bool_field!(3, win0_bg3, with_win0_bg3);
  u16_bool_field!(4, win0_obj, with_win0_obj);
  u16_bool_field!(5, win0_effect, with_win0_effect);
  u16_bool_field!(8, win1_bg0, with_win1_bg0);
  u16_bool_field!(9, win1_bg1, with_win1_bg1);
  u16_bool_field!(10, win1_bg2, with_win1_bg2);
  u16_bool_field!(11, win1_bg3, with_win1_bg3);
  u16_bool_field!(12, win1_obj, with_win1_obj);
  u16_bool_field!(13, win1_effect, with_win1_effect);
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct WindowOutside(u16);
impl WindowOutside {
  pub_const_fn_new_zeroed!();
  u16_bool_field!(0, outside_bg0, with_outside_bg0);
  u16_bool_field!(1, outside_bg1, with_outside_bg1);
  u16_bool_field!(2, outside_bg2, with_outside_bg2);
  u16_bool_field!(3, outside_bg3, with_outside_bg3);
  u16_bool_field!(4, outside_obj, with_outside_obj);
  u16_bool_field!(5, outside_effect, with_outside_effect);
  u16_bool_field!(8, obj_win_bg0, with_obj_win_bg0);
  u16_bool_field!(9, obj_win_bg1, with_obj_win_bg1);
  u16_bool_field!(10, obj_win_bg2, with_obj_win_bg2);
  u16_bool_field!(11, obj_win_bg3, with_obj_win_bg3);
  u16_bool_field!(12, obj_win_obj, with_obj_win_obj);
  u16_bool_field!(13, obj_win_effect, with_obj_win_effect);
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct Mosaic(u16);
impl Mosaic {
  pub_const_fn_new_zeroed!();
  u16_int_field!(0 - 3, bg_h_extra, with_bg_h_extra);
  u16_int_field!(4 - 7, bg_v_extra, with_bg_v_extra);
  u16_int_field!(8 - 11, obj_h_extra, with_obj_h_extra);
  u16_int_field!(12 - 15, obj_v_extra, with_obj_v_extra);
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u16)]
pub enum ColorEffectMode {
  #[default]
  NoEffect = 0 << 6,
  AlphaBlend = 1 << 6,
  Brighten = 2 << 6,
  Darken = 3 << 6,
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct BlendControl(u16);
impl BlendControl {
  pub_const_fn_new_zeroed!();
  u16_bool_field!(0, target1_bg0, with_target1_bg0);
  u16_bool_field!(1, target1_bg1, with_target1_bg1);
  u16_bool_field!(2, target1_bg2, with_target1_bg2);
  u16_bool_field!(3, target1_bg3, with_target1_bg3);
  u16_bool_field!(4, target1_obj, with_target1_obj);
  u16_bool_field!(5, target1_backdrop, with_target1_backdrop);
  u16_enum_field!(6 - 7: ColorEffectMode, mode, with_mode);
  u16_bool_field!(8, target2_bg0, with_target2_bg0);
  u16_bool_field!(9, target2_bg1, with_target2_bg1);
  u16_bool_field!(10, target2_bg2, with_target2_bg2);
  u16_bool_field!(11, target2_bg3, with_target2_bg3);
  u16_bool_field!(12, target2_obj, with_target2_obj);
  u16_bool_field!(13, target2_backdrop, with_target2_backdrop);
}

/// Data for a 4-bit-per-pixel tile.
pub type Tile4 = [u32; 8];

/// Data for an 8-bit-per-pixel tile.
pub type Tile8 = [u32; 16];

/// An entry within a tile mode tilemap.
///
/// * `tile` is the index of the tile, offset from the `charblock` that the
///   background is using. This is a 10-bit value, so indexes are in the range
///   `0..=1023`. You *cannot* index past the end of background VRAM into object
///   VRAM (it just won't draw properly), but you *can* index past the end of
///   one charblock into the next charblock.
/// * `hflip` If you want the tile horizontally flipped.
/// * `vflip` If you want the tile vertically flipped.
/// * `palbank` sets the palbank for this tile. If the background is in 8bpp
///   mode this has no effect.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct TextEntry(u16);
impl TextEntry {
  pub_const_fn_new_zeroed!();
  u16_int_field!(0 - 9, tile, with_tile);
  u16_bool_field!(10, hflip, with_hflip);
  u16_bool_field!(11, vflip, with_vflip);
  u16_int_field!(12 - 15, palbank, with_palbank);

  /// Shorthand for `TextEntry::new().with_tile(id)`
  #[inline]
  #[must_use]
  pub const fn from_tile(id: u16) -> Self {
    Self(id & 0b11_1111_1111)
  }
}