summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBad Diode <bd@badd10de.dev>2021-04-26 11:11:36 +0200
committerBad Diode <bd@badd10de.dev>2021-04-26 11:11:36 +0200
commit24887f28c3eb6d67a7c1a0803520b3fb728ee4f3 (patch)
tree3816d8f7f4e1d3d6c4bb6caf084cbb06d93e941c
parent0f16e5bfb1738330a33b300067f86d363fd250bf (diff)
downloadgba-experiments-24887f28c3eb6d67a7c1a0803520b3fb728ee4f3.tar.gz
gba-experiments-24887f28c3eb6d67a7c1a0803520b3fb728ee4f3.zip
Move code to relevant files for organization
-rw-r--r--src/bitmap.h228
-rw-r--r--src/common.h281
-rw-r--r--src/gba-buttons.c602
-rw-r--r--src/main.c1184
-rw-r--r--src/sprites.h98
5 files changed, 1212 insertions, 1181 deletions
diff --git a/src/bitmap.h b/src/bitmap.h
new file mode 100644
index 0000000..0befe5d
--- /dev/null
+++ b/src/bitmap.h
@@ -0,0 +1,228 @@
1#ifndef GBAEXP_BITMAP_H
2#define GBAEXP_BITMAP_H
3
4#include "bd-font.c"
5#include "common.c"
6
7// Using bd-font, an 8x8 bitmap font.
8static void
9put_char(int x, int y, Color clr, u8 chr) {
10 for (size_t i = 0; i < 8; ++i) {
11 for (size_t j = 0; j < 8; ++j) {
12 if ((font[chr][i] >> (7 - j)) & 0x1) {
13 FRAMEBUFFER[y + i][x + j] = clr;
14 }
15 }
16 }
17}
18
19static void
20put_text(int x, int y, Color clr, char *msg) {
21 int count = 0;
22 while (*msg) {
23 put_char(x + count, y, clr, *msg++);
24 count += 8;
25 }
26}
27
28// Draws a line with the given color between (x0,y0) and (x1,y1) using the
29// Bresenham's line drawing algorithm using exclusively integer arithmetic.
30static void
31draw_line(int x0, int y0, int x1, int y1, Color clr) {
32 // Pointer to the initial position of the screen buffer where we will start
33 // writing our data.
34 vu16 *destination = (u16*)(SCREEN_BUFFER + y0 * SCREEN_WIDTH + x0);
35
36 // Adjust the step direction and calculate deltas.
37 int x_step;
38 int y_step;
39 int dx;
40 int dy;
41 if (x0 > x1) {
42 x_step = -1;
43 dx = x0 - x1;
44 } else {
45 x_step = 1;
46 dx = x1 - x0;
47 }
48 if (y0 > y1) {
49 y_step = -SCREEN_WIDTH;
50 dy = y0 - y1;
51 } else {
52 y_step = +SCREEN_WIDTH;
53 dy = y1 - y0;
54 }
55
56 if(dy == 0) {
57 // Horizontal line.
58 for(int i = 0; i <= dx; i++) {
59 destination[i * x_step] = clr;
60 }
61 } else if(dx == 0) {
62 // Vertical line.
63 for(int i = 0; i <= dy; i++) {
64 destination[i * y_step] = clr;
65 }
66 } else if (dx >= dy){
67 // Positive slope.
68 int diff = 2 * dy - dx;
69 for (int i = 0; i <= dx; ++i) {
70 *destination = clr;
71 if (diff >= 0) {
72 destination += y_step;
73 diff -= 2 * dx;
74 }
75 destination += x_step;
76 diff += 2 * dy;
77 }
78 } else {
79 // Negative slope.
80 int diff = 2 * dx - dy;
81 for (int i = 0; i <= dy; ++i) {
82 *destination = clr;
83 if (diff >= 0) {
84 destination += x_step;
85 diff -= 2 * dy;
86 }
87 destination += y_step;
88 diff += 2 * dx;
89 }
90 }
91}
92
93static inline void
94draw_rect(int x0, int y0, int x1, int y1, Color clr) {
95 if (x0 > x1) {
96 int tmp = x0;
97 x0 = x1;
98 x1 = tmp;
99 }
100 if (y0 > y1) {
101 int tmp = y0;
102 y0 = y1;
103 y1 = tmp;
104 }
105 int dx = x1 - x0;
106 int dy = y1 - y0;
107 for (int i = 0; i <= dx; ++i) {
108 int x = x0 + i;
109 FRAMEBUFFER[y0][x] = clr;
110 FRAMEBUFFER[y1][x] = clr;
111 }
112 for (int j = 0; j <= dy; ++j) {
113 int y = y0 + j;
114 FRAMEBUFFER[y][x0] = clr;
115 FRAMEBUFFER[y][x1] = clr;
116 }
117}
118
119static inline void
120draw_fill_rect(int x0, int y0, int x1, int y1, Color clr) {
121 if (x0 > x1) {
122 int tmp = x0;
123 x0 = x1;
124 x1 = tmp;
125 }
126 if (y0 > y1) {
127 int tmp = y0;
128 y0 = y1;
129 y1 = tmp;
130 }
131 int dx = x1 - x0;
132 int dy = y1 - y0;
133 for (int i = 0; i <= dx; ++i) {
134 for (int j = 0; j <= dy; ++j) {
135 int x = x0 + i;
136 int y = y0 + j;
137 FRAMEBUFFER[y][x] = clr;
138 }
139 }
140}
141
142// In Mode4 the buffer is of 8 bytes per pixel instead of 16. We can't write the
143// color directly, instead the color is stored in the palette memory at
144// `MEM_PAL`. Note that in this mode MEM_PAL[0] is the background color. This
145// plotter takes an index to a color stored in MEM_PAL[col_index]. Because the
146// GBA needs to meet memory alignment requirements, we can't write a u8 into
147// memory, instead we need to read a u16 word, mask and or the corresponding
148// bits and wave the updated u16.
149static void
150put_pixel_m4(int x, int y, u8 col_index, vu16 *buffer) {
151 int buffer_index = (y * SCREEN_WIDTH + x) / 2;
152 vu16 *destination = &buffer[buffer_index];
153 // Odd pixels will go to the top 8 bits of the destination. Even pixels to
154 // the lower 8 bits.
155 int odd = x & 0x1;
156 if(odd) {
157 *destination= (*destination & 0xFF) | (col_index << 8);
158 } else {
159 *destination= (*destination & ~0xFF) | col_index;
160 }
161}
162
163static void
164draw_fill_rect_m4(int x0, int y0, int x1, int y1, u8 col_index, vu16 *buffer) {
165 int ix, iy;
166 for(iy = y0; iy < y1; iy++) {
167 for(ix = x0; ix < x1; ix++) {
168 put_pixel_m4(ix, iy, col_index, buffer);
169 }
170 }
171}
172
173void
174draw_logo(void) {
175 int side = 60;
176 int line = 35;
177 int height = side * 0.5;
178 int x = SCREEN_WIDTH / 2 - height / 2;
179 int y = SCREEN_HEIGHT / 2;
180
181 // Draw red triangle.
182 draw_line(x + height - 1, y - side / 2, x, y - 1, COLOR_RED);
183 draw_line(x + height - 1, y + side / 2, x, y + 1, COLOR_RED);
184 draw_line(x + height - 1, y - side / 2 + 1, x, y, COLOR_RED);
185 draw_line(x + height - 1, y + side / 2 - 1, x, y, COLOR_RED);
186
187 // Draw white triangle.
188 draw_line(x, y - side / 2, x, y + side / 2, COLOR_WHITE);
189 draw_line(x + 1, y - side / 2, x + height, y - 1, COLOR_WHITE);
190 draw_line(x + 1, y + side / 2, x + height, y + 1, COLOR_WHITE);
191
192 // Draw white line at triangle tip.
193 draw_line(x + height, y - side / 2, x + height, y + side / 2, COLOR_WHITE);
194 draw_line(x + height + 1, y - side / 2, x + height + 1, y + side / 2, COLOR_WHITE);
195
196 // Double triangle line.
197 draw_line(x - 1, y - side / 2, x - 1, y + side / 2, COLOR_WHITE);
198 draw_line(x + 1, y - side / 2 + 1, x + height, y, COLOR_WHITE);
199 draw_line(x + 1, y + side / 2 - 1, x + height, y, COLOR_WHITE);
200
201 // Draw white lines.
202 draw_line(x - line, y, x, y, COLOR_WHITE);
203 draw_line(x + height, y, x + height + line, y, COLOR_WHITE);
204 draw_line(x - line, y + 1, x, y + 1, COLOR_WHITE);
205 draw_line(x + height, y + 1, x + height + line, y + 1, COLOR_WHITE);
206}
207
208void
209copy_font_to_tile_memory(Tile *tile) {
210 // Hex to bits translation table.
211 const u32 conversion_u32[16] = {
212 0x00000000, 0x00001000, 0x00000100, 0x00001100,
213 0x00000010, 0x00001010, 0x00000110, 0x00001110,
214 0x00000001, 0x00001001, 0x00000101, 0x00001101,
215 0x00000011, 0x00001011, 0x00000111, 0x00001111,
216 };
217 for (size_t i = 0; i < 250; ++i) {
218 for (size_t j = 0; j < 8; ++j) {
219 u8 row = font[i][j];
220 u32 tile_idx = 0x00000000;
221 tile_idx = conversion_u32[row & 0xF] << 16;
222 tile_idx |= conversion_u32[(row >> 4) & 0xF];
223 (tile + i)->data[j] = tile_idx;
224 }
225 }
226}
227
228#endif // GBAEXP_BITMAP_H
diff --git a/src/common.h b/src/common.h
new file mode 100644
index 0000000..35b4619
--- /dev/null
+++ b/src/common.h
@@ -0,0 +1,281 @@
1#ifndef GBAEXP_COMMON_H
2#define GBAEXP_COMMON_H
3
4#include "shorthand.h"
5
6//
7// Memory sections.
8//
9
10// Defines for the different memory sections in the GBA.
11#define MEM_SROM 0x00000000
12#define MEM_EW 0x02000000
13#define MEM_IW 0x03000000
14#define MEM_IO 0x04000000
15#define MEM_PAL 0x05000000
16#define MEM_VRAM 0x06000000
17#define MEM_OAM 0x07000000
18#define MEM_PAK 0x08000000
19#define MEM_CART 0x0E000000
20
21//
22// Display modes.
23//
24
25// Display registers.
26#define DISP_CTRL *((vu32*)(MEM_IO + 0x0000))
27#define DISP_STATUS *((vu16*)(MEM_IO + 0x0004))
28#define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006))
29
30// The first three bits in the DISP_CTRL are used to control the video mode.
31#define DISP_MODE_0 0x0000
32#define DISP_MODE_1 0x0001
33#define DISP_MODE_2 0x0002
34#define DISP_MODE_3 0x0003
35#define DISP_MODE_4 0x0004
36#define DISP_MODE_5 0x0005
37#define DISP_GB (1 << 3)
38#define DISP_PAGE (1 << 4)
39#define DISP_OAM_HBLANK (1 << 5)
40#define DISP_OBJ_1D (1 << 6)
41#define DISP_BLANK (1 << 7)
42#define DISP_BG_0 (1 << 8)
43#define DISP_BG_1 (1 << 9)
44#define DISP_BG_2 (1 << 10)
45#define DISP_BG_3 (1 << 11)
46#define DISP_OBJ (1 << 12)
47#define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D
48
49// Registers to control of BG layers.
50#define BG_CTRL_0 *((vu16*)(0x04000008 + 0x0002 * 0))
51#define BG_CTRL_1 *((vu16*)(0x04000008 + 0x0002 * 1))
52#define BG_CTRL_2 *((vu16*)(0x04000008 + 0x0002 * 2))
53#define BG_CTRL_3 *((vu16*)(0x04000008 + 0x0002 * 3))
54
55// Bits to control the background.
56#define BG_PRIORITY_0 0x0
57#define BG_PRIORITY_1 0x1
58#define BG_PRIORITY_2 0x2
59#define BG_PRIORITY_3 0x3
60#define BG_CHARBLOCK(N) ((N) << 2)
61#define BG_MOSAIC (1 << 6)
62#define BG_HIGH_COLOR (1 << 7)
63#define BG_SCREENBLOCK(N) ((N) << 8)
64#define BG_AFFINE (1 << 0xD)
65#define BG_SIZE(N) ((N) << 0xE)
66
67// BG registers for horizontal displacement.
68#define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0))
69#define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1))
70#define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2))
71#define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3))
72
73// BG registers for vertical displacement.
74#define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0))
75#define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1))
76#define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2))
77#define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3))
78
79// Screen settings.
80#define SCREEN_WIDTH 240
81#define SCREEN_HEIGHT 160
82
83// The GBA in mode 3 expects rbg15 colors in the VRAM, where each component
84// (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0).
85typedef u16 Color;
86
87//
88// Tile memory access.
89//
90
91// NOTE: Only defining 4bpp tiles for now.
92typedef struct Tile {
93 u32 data[8];
94} Tile;
95
96typedef Tile TileBlock[512];
97#define TILE_MEM ((TileBlock*) MEM_VRAM)
98
99typedef u16 ScreenBlock[1024];
100#define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM)
101
102// We can treat the screen as a HxW matrix. With the following macro we can
103// write a pixel to the screen at the (x, y) position using:
104//
105// FRAMEBUFFER[y][x] = color;
106//
107typedef Color Scanline[SCREEN_WIDTH];
108#define FRAMEBUFFER ((Scanline*)MEM_VRAM)
109#define SCREEN_BUFFER ((u16*) MEM_VRAM)
110#define PAL_BUFFER_BG ((u16*) MEM_PAL)
111#define PAL_BUFFER_SPRITES ((u16*) 0x05000200)
112
113//
114// Colors.
115//
116
117static inline Color
118rgb15(u32 red, u32 green, u32 blue ) {
119 return (blue << 10) | (green << 5) | red;
120}
121
122#define COLOR_RED rgb15(31, 0, 12)
123#define COLOR_BLUE rgb15(2, 15, 30)
124#define COLOR_CYAN rgb15(0, 30, 30)
125#define COLOR_GREY rgb15(4, 4, 4)
126#define COLOR_BLACK rgb15(0, 0, 0)
127#define COLOR_WHITE rgb15(28, 28, 28)
128
129//
130// Sprites.
131//
132
133// Using macros instead of aligned structs for setting up OBJ attributes and
134// affine parameters.
135// TODO: Benchmark if this would be slower or the same that TONC's
136// implementation.
137#define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N)))
138#define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N)))
139#define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N)))
140#define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N)))
141#define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N)))
142#define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N)))
143#define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N)))
144
145// OBJ_ATTR_0 parameters
146#define OBJ_Y_COORD(N) ((N) & 0xFF)
147#define OBJ_NORMAL (0x00 << 0x8)
148#define OBJ_AFFINE (0x01 << 0x8)
149#define OBJ_HIDDEN (0x02 << 0x8)
150#define OBJ_AFFINE_2X (0x03 << 0x8)
151#define OBJ_ALPHA_BLEND (0x01 << 0xA)
152#define OBJ_WINDOW (0x02 << 0xA)
153#define OBJ_SHAPE_SQUARE (0x00 << 0xE)
154#define OBJ_SHAPE_WIDE (0x01 << 0xE)
155#define OBJ_SHAPE_TALL (0x02 << 0xE)
156
157// OBJ_ATTR_1 parameters
158#define OBJ_X_COORD(N) ((N) & 0x1FF)
159#define OBJ_AFFINE_IDX(N) ((N) << 0x9)
160#define OBJ_H_FLIP (0x01 << 0xC)
161#define OBJ_V_FLIP (0x01 << 0xD)
162#define OBJ_SIZE_SMALL (0x00 << 0xE)
163#define OBJ_SIZE_MID (0x01 << 0xE)
164#define OBJ_SIZE_BIG (0x02 << 0xE)
165#define OBJ_SIZE_HUGE (0x03 << 0xE)
166
167// OBJ_ATTR_2 parameters
168#define OBJ_TILE_INDEX(N) ((N) & 0x3FF)
169#define OBJ_PRIORITY(N) ((N) << 0xA)
170#define OBJ_PAL_BANK(N) ((N) << 0xC)
171
172static inline void
173wait_vsync(void) {
174 while(DISP_VCOUNT >= 160);
175 while(DISP_VCOUNT < 160);
176}
177
178//
179// Mode 4 page flipping
180//
181
182static inline void
183flip_page(void) {
184 DISP_CTRL ^= DISP_PAGE;
185}
186
187#define SCREEN_PAGE_1 ((vu16*) MEM_VRAM)
188#define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000))
189
190//
191// Profiling.
192//
193
194#define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0))
195#define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1))
196#define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2))
197#define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3))
198#define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0))
199#define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1))
200#define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2))
201#define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3))
202
203// Timer control bits.
204#define TIMER_CTRL_FREQ_0 0
205#define TIMER_CTRL_FREQ_1 1
206#define TIMER_CTRL_FREQ_2 2
207#define TIMER_CTRL_FREQ_3 3
208#define TIMER_CTRL_CASCADE (1 << 2)
209#define TIMER_CTRL_IRQ (1 << 6)
210#define TIMER_CTRL_ENABLE (1 << 7)
211
212// We use timers 2 and 3 to count the number of cycles since the profile_start
213// functions is called. Don't use if the code we are trying to profile make use
214// of these timers.
215static inline
216void profile_start(void) {
217 TIMER_DATA_2 = 0;
218 TIMER_DATA_3 = 0;
219 TIMER_CTRL_2 = 0;
220 TIMER_CTRL_3 = 0;
221 TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE;
222 TIMER_CTRL_2 = TIMER_CTRL_ENABLE;
223}
224
225static inline
226u32 profile_stop(void) {
227 TIMER_CTRL_2 = 0;
228 return (TIMER_DATA_3 << 16) | TIMER_DATA_2;
229}
230
231//
232// Input handling.
233//
234
235// Memory address for key input register
236#define KEY_INPUTS *((vu16*) 0x04000130)
237
238// Alias for key pressing bits.
239#define KEY_A (1 << 0)
240#define KEY_B (1 << 1)
241#define KEY_SELECT (1 << 2)
242#define KEY_START (1 << 3)
243#define KEY_RIGHT (1 << 4)
244#define KEY_LEFT (1 << 5)
245#define KEY_UP (1 << 6)
246#define KEY_DOWN (1 << 7)
247#define KEY_R (1 << 8)
248#define KEY_L (1 << 9)
249
250#define KEY_MASK 0x03FF
251
252// Saving the previous and current key states as globals for now.
253static u16 key_curr = 0;
254static u16 key_prev = 0;
255
256static inline void
257poll_keys(void) {
258 key_prev = key_curr;
259 key_curr = ~KEY_INPUTS & KEY_MASK;
260}
261
262// Returns true if the given key has been pressed at time of calling and was not
263// pressed since the previous call. For example, if a key is being held, this
264// function will return `true` only on the frame where the key initially
265// activated.
266static inline u32
267key_pressed(u32 key) {
268 return (key_curr & key) & ~(key_prev & key);
269}
270
271// Check if the given key is pressed and has been since at least one frame.
272static inline u32
273key_hold(u32 key) {
274 return (key_curr & key) & key_prev & key;
275}
276
277// Check if the given key/button is currently pressed.
278#define KEY_PRESSED(key) (~(KEY_INPUTS) & key)
279
280
281#endif // GBAEXP_COMMON_H
diff --git a/src/gba-buttons.c b/src/gba-buttons.c
index 951b4dc..dd06351 100644
--- a/src/gba-buttons.c
+++ b/src/gba-buttons.c
@@ -1,3 +1,6 @@
1#include "common.h"
2#include "sprites.h"
3
1// 1bpp packed 4// 1bpp packed
2u32 gba_btn_a_data[] = { 5u32 gba_btn_a_data[] = {
3 0xc0300804, 0x844242c2, 0x030c1020, 0x21424243, 6 0xc0300804, 0x844242c2, 0x030c1020, 0x21424243,
@@ -133,11 +136,6 @@ u32 gba_btn_fx_startselect[] = {
133 0x01800000, 0x00000000, 0x00000000, 0x00000004, 136 0x01800000, 0x00000000, 0x00000000, 0x00000004,
134}; 137};
135 138
136typedef struct SpriteAnimation {
137 size_t *tile_offsets;
138 size_t n_frames;
139} SpriteAnimation;
140
141typedef enum {BTN_STATE_IDLE, BTN_STATE_PRESSED, BTN_STATE_RELEASED, BTN_STATE_HOLD} BtnState; 139typedef enum {BTN_STATE_IDLE, BTN_STATE_PRESSED, BTN_STATE_RELEASED, BTN_STATE_HOLD} BtnState;
142 140
143typedef struct AnimationEntry { 141typedef struct AnimationEntry {
@@ -262,3 +260,597 @@ AnimationEntry *btn_animation_startselect[] = {
262 {0, 0, 12, 1}, 260 {0, 0, 12, 1},
263 }, 261 },
264}; 262};
263
264typedef struct ButtonSprite {
265 ObjState *sprites;
266 AnimationEntry **animations;
267 size_t frame;
268 size_t n_obj;
269 size_t n_frames;
270 BtnState state;
271} ButtonSprite;
272
273void
274init_button_sprite(ButtonSprite *btn) {
275 for (size_t i = 0; i < btn->n_obj; ++i) {
276 btn->sprites[i].id = load_packed_sprite_data(
277 btn->sprites[i].data,
278 btn->sprites[i].n_tiles,
279 btn->sprites[i].frames);
280 btn->sprites[i].base_tile = sprites[btn->sprites[i].id].tile_start;
281 }
282}
283
284void
285button_tick(ButtonSprite *btn) {
286 // Nothing to do here.
287 if (btn->state == BTN_STATE_IDLE) {
288 return;
289 }
290
291 // Reset animation state.
292 if (btn->state == BTN_STATE_PRESSED && btn->frame != 0) {
293 btn->frame = 0;
294 }
295
296 // Continue the animation.
297 if (btn->state == BTN_STATE_HOLD || btn->state == BTN_STATE_PRESSED ) {
298 if(btn->frame < btn->n_frames - 1) {
299 btn->frame++;
300 }
301 }
302
303 // Finish the animation and return to idle.
304 if (btn->state == BTN_STATE_RELEASED) {
305 if (btn->frame > 0 && btn->frame < btn->n_frames - 1) {
306 btn->frame++;
307 } else {
308 btn->frame = 0;
309 btn->state = BTN_STATE_IDLE;
310 }
311 }
312 for (size_t i = 0; i < btn->n_obj; ++i) {
313 AnimationEntry anim_frame = btn->animations[i][btn->frame];
314 int x = btn->sprites[i].x + anim_frame.x_offset;
315 int y = btn->sprites[i].y + anim_frame.y_offset;
316 int base_tile = btn->sprites[i].base_tile + anim_frame.tile_offset;
317
318 // Clear the previous x/y coordinate and base tiles.
319 btn->sprites[i].obj_attr_0 &= ~0xFF;
320 btn->sprites[i].obj_attr_1 &= ~0x1FF;
321 btn->sprites[i].obj_attr_2 &= ~0x3FF;
322
323 // Update x/y/tile and hidden state from the animations.
324 btn->sprites[i].obj_attr_0 |= OBJ_Y_COORD(y);
325 btn->sprites[i].obj_attr_1 |= OBJ_X_COORD(x);
326 btn->sprites[i].obj_attr_2 |= base_tile;
327 if (anim_frame.hidden) {
328 btn->sprites[i].obj_attr_0 |= OBJ_HIDDEN;
329 } else {
330 btn->sprites[i].obj_attr_0 &= ~OBJ_HIDDEN;
331 }
332
333 // Update OBJ attributes.
334 OBJ_ATTR_0(btn->sprites[i].id) = btn->sprites[i].obj_attr_0;
335 OBJ_ATTR_1(btn->sprites[i].id) = btn->sprites[i].obj_attr_1;
336 OBJ_ATTR_2(btn->sprites[i].id) = btn->sprites[i].obj_attr_2;
337 }
338}
339
340ButtonSprite buttons[] = {
341 // DOWN.
342 {
343 .frame = 0,
344 .n_obj = 3,
345 .n_frames = 8,
346 .state = BTN_STATE_RELEASED,
347 .animations = &btn_animation,
348 .sprites = &(ObjState[]){
349 {
350 .id = 0,
351 .x = SCREEN_WIDTH / 2 - 64 - 16,
352 .y = SCREEN_HEIGHT / 2 + 29,
353 .data = &gba_btn_updown_data,
354 .n_tiles = 4,
355 .frames = 1,
356 .obj_attr_0 = 0,
357 .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID,
358 .obj_attr_2 = 0
359 },
360 {
361 .id = 0,
362 .x = SCREEN_WIDTH / 2 - 64 - 16,
363 .y = SCREEN_HEIGHT / 2 + 29 + 11,
364 .data = &gba_btn_down_shadow_data,
365 .n_tiles = 2,
366 .frames = 1,
367 .obj_attr_0 = OBJ_SHAPE_WIDE,
368 .obj_attr_1 = OBJ_SIZE_SMALL,
369 .obj_attr_2 = 0
370 },
371 {
372 .id = 0,
373 .x = SCREEN_WIDTH / 2 - 64 - 16 - 8,
374 .y = SCREEN_HEIGHT / 2 + 29 + 17,
375 .data = &gba_btn_fx_downup,
376 .n_tiles = 4,
377 .frames = 4,
378 .obj_attr_0 = OBJ_SHAPE_WIDE,
379 .obj_attr_1 = OBJ_SIZE_MID,
380 .obj_attr_2 = 0
381 },
382 },
383 },
384 // UP.
385 {
386 .frame = 0,
387 .n_obj = 3,
388 .n_frames = 8,
389 .state = BTN_STATE_RELEASED,
390 .animations = &btn_animation,
391 .sprites = &(ObjState[]){
392 {
393 .id = 0,
394 .x = SCREEN_WIDTH / 2 - 64 - 16,
395 .y = SCREEN_HEIGHT / 2 + 32 - 18,
396 .data = &gba_btn_updown_data,
397 .n_tiles = 4,
398 .frames = 1,
399 .obj_attr_0 = 0,
400 .obj_attr_1 = OBJ_SIZE_MID,
401 .obj_attr_2 = 0
402 },
403 {
404 .id = 0,
405 .x = SCREEN_WIDTH / 2 - 64 - 16,
406 .y = SCREEN_HEIGHT / 2 + 32 - 18 + 7,
407 .data = &gba_btn_up_shadow_data,
408 .n_tiles = 2,
409 .frames = 1,
410 .obj_attr_0 = OBJ_SHAPE_WIDE,
411 .obj_attr_1 = OBJ_SIZE_SMALL,
412 .obj_attr_2 = 0
413 },
414 {
415 .id = 0,
416 .x = SCREEN_WIDTH / 2 - 64 - 16 - 8,
417 .y = SCREEN_HEIGHT / 2 + 32 - 18 - 7,
418 .data = &gba_btn_fx_downup,
419 .n_tiles = 4,
420 .frames = 4,
421 .obj_attr_0 = OBJ_SHAPE_WIDE,
422 .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID,
423 .obj_attr_2 = 0
424 },
425 },
426 },
427 // LEFT
428 {
429 .frame = 0,
430 .n_obj = 3,
431 .n_frames = 8,
432 .state = BTN_STATE_RELEASED,
433 .animations = &btn_animation,
434 .sprites = &(ObjState[]){
435 {
436 .id = 0,
437 .x = SCREEN_WIDTH / 2 - 64 - 16 - 10,
438 .y = SCREEN_HEIGHT / 2 + 32 - 10,
439 .data = &gba_btn_leftright_data,
440 .n_tiles = 4,
441 .frames = 1,
442 .obj_attr_0 = 0,
443 .obj_attr_1 = OBJ_SIZE_MID,
444 .obj_attr_2 = 0
445 },
446 {
447 .id = 0,
448 .x = SCREEN_WIDTH / 2 - 64 - 16 - 10,
449 .y = SCREEN_HEIGHT / 2 + 32 - 10 + 6,
450 .data = &gba_btn_leftright_shadow_data,
451 .n_tiles = 2,
452 .frames = 1,
453 .obj_attr_0 = OBJ_SHAPE_WIDE,
454 .obj_attr_1 = OBJ_SIZE_SMALL,
455 .obj_attr_2 = 0
456 },
457 {
458 .id = 0,
459 .x = SCREEN_WIDTH / 2 - 64 - 16 - 10 - 6,
460 .y = SCREEN_HEIGHT / 2 + 32 - 10 - 8,
461 .data = &gba_btn_fx_leftright,
462 .n_tiles = 4,
463 .frames = 4,
464 .obj_attr_0 = OBJ_SHAPE_TALL,
465 .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID,
466 .obj_attr_2 = 0
467 },
468 },
469 },
470 // RIGHT.
471 {
472 .frame = 0,
473 .n_obj = 3,
474 .n_frames = 8,
475 .state = BTN_STATE_RELEASED,
476 .animations = &btn_animation,
477 .sprites = &(ObjState[]){
478 {
479 .id = 0,
480 .x = SCREEN_WIDTH / 2 - 64 - 16 + 11,
481 .y = SCREEN_HEIGHT / 2 + 32 - 10,
482 .data = &gba_btn_leftright_data,
483 .n_tiles = 4,
484 .frames = 1,
485 .obj_attr_0 = 0,
486 .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID,
487 .obj_attr_2 = 0
488 },
489 {
490 .id = 0,
491 .x = SCREEN_WIDTH / 2 - 64 - 16 + 11,
492 .y = SCREEN_HEIGHT / 2 + 32 - 10 + 6,
493 .data = &gba_btn_leftright_shadow_data,
494 .n_tiles = 2,
495 .frames = 1,
496 .obj_attr_0 = OBJ_SHAPE_WIDE,
497 .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_SMALL,
498 .obj_attr_2 = 0
499 },
500 {
501 .id = 0,
502 .x = SCREEN_WIDTH / 2 - 64 - 16 + 11 + 14,
503 .y = SCREEN_HEIGHT / 2 + 32 - 10 - 8,
504 .data = &gba_btn_fx_leftright,
505 .n_tiles = 4,
506 .frames = 4,
507 .obj_attr_0 = OBJ_SHAPE_TALL,
508 .obj_attr_1 = OBJ_SIZE_MID,
509 .obj_attr_2 = 0
510 },
511 },
512 },
513 // L.
514 {
515 .frame = 0,
516 .n_obj = 3,
517 .n_frames = 8,
518 .state = BTN_STATE_RELEASED,
519 .animations = &btn_animation,
520 .sprites = &(ObjState[]){
521 {
522 .id = 0,
523 .x = SCREEN_WIDTH / 2 - 64 - 28,
524 .y = SCREEN_HEIGHT / 2 - 32 - 20,
525 .data = &gba_btn_l_data,
526 .n_tiles = 2,
527 .frames = 1,
528 .obj_attr_0 = OBJ_SHAPE_WIDE,
529 .obj_attr_1 = OBJ_SIZE_SMALL,
530 .obj_attr_2 = 0
531 },
532 {
533 .id = 0,
534 .x = SCREEN_WIDTH / 2 - 64 - 28,
535 .y = SCREEN_HEIGHT / 2 - 32 - 20 + 2,
536 .data = &gba_btn_lr_shadow_data,
537 .n_tiles = 2,
538 .frames = 1,
539 .obj_attr_0 = OBJ_SHAPE_WIDE,
540 .obj_attr_1 = OBJ_SIZE_SMALL,
541 .obj_attr_2 = 0
542 },
543 {
544 .id = 0,
545 .x = SCREEN_WIDTH / 2 - 64 - 28 - 12,
546 .y = SCREEN_HEIGHT / 2 - 32 - 20 - 12,
547 .data = &gba_btn_fx_lr,
548 .n_tiles = 4,
549 .frames = 4,
550 .obj_attr_0 = OBJ_SHAPE_SQUARE,
551 .obj_attr_1 = OBJ_SIZE_MID,
552 .obj_attr_2 = 0
553 },
554 },
555 },
556 // R.
557 {
558 .frame = 0,
559 .n_obj = 3,
560 .n_frames = 8,
561 .state = BTN_STATE_RELEASED,
562 .animations = &btn_animation,
563 .sprites = &(ObjState[]){
564 {
565 .id = 0,
566 .x = SCREEN_WIDTH / 2 + 32 + 20 + 24,
567 .y = SCREEN_HEIGHT / 2 - 32 - 20,
568 .data = &gba_btn_r_data,
569 .n_tiles = 2,
570 .frames = 1,
571 .obj_attr_0 = OBJ_SHAPE_WIDE,
572 .obj_attr_1 = OBJ_SIZE_SMALL,
573 .obj_attr_2 = 0
574 },
575 {
576 .id = 0,
577 .x = SCREEN_WIDTH / 2 + 32 + 20 + 24,
578 .y = SCREEN_HEIGHT / 2 - 32 - 20 + 2,
579 .data = &gba_btn_lr_shadow_data,
580 .n_tiles = 2,
581 .frames = 1,
582 .obj_attr_0 = OBJ_SHAPE_WIDE,
583 .obj_attr_1 = OBJ_SIZE_SMALL,
584 .obj_attr_2 = 0
585 },
586 {
587 .id = 0,
588 .x = SCREEN_WIDTH / 2 + 32 + 20 + 12 + 24,
589 .y = SCREEN_HEIGHT / 2 - 32 - 20 - 12,
590 .data = &gba_btn_fx_lr,
591 .n_tiles = 4,
592 .frames = 4,
593 .obj_attr_0 = OBJ_SHAPE_SQUARE,
594 .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID,
595 .obj_attr_2 = 0
596 },
597 },
598 },
599 // A.
600 {
601 .frame = 0,
602 .n_obj = 3,
603 .n_frames = 8,
604 .state = BTN_STATE_RELEASED,
605 .animations = &btn_animation_ab,
606 .sprites = &(ObjState[]){
607 {
608 .id = 0,
609 .x = SCREEN_WIDTH / 2 + 32 + 20 + 20,
610 .y = SCREEN_HEIGHT / 2 + 32 - 16,
611 .data = &gba_btn_a_data,
612 .n_tiles = 4,
613 .frames = 1,
614 .obj_attr_0 = OBJ_SHAPE_SQUARE,
615 .obj_attr_1 = OBJ_SIZE_MID,
616 .obj_attr_2 = 0
617 },
618 {
619 .id = 0,
620 .x = SCREEN_WIDTH / 2 + 32 + 20 + 20,
621 .y = SCREEN_HEIGHT / 2 + 32 - 16 + 8,
622 .data = &gba_btn_ab_shadow_data,
623 .n_tiles = 2,
624 .frames = 1,
625 .obj_attr_0 = OBJ_SHAPE_WIDE,
626 .obj_attr_1 = OBJ_SIZE_SMALL,
627 .obj_attr_2 = 0
628 },
629 {
630 .id = 0,
631 .x = SCREEN_WIDTH / 2 + 32 + 19 - 7 + 20,
632 .y = SCREEN_HEIGHT / 2 + 32 - 23,
633 .data = &gba_btn_fx_ab,
634 .n_tiles = 16,
635 .frames = 3,
636 .obj_attr_0 = OBJ_SHAPE_SQUARE,
637 .obj_attr_1 = OBJ_SIZE_BIG,
638 .obj_attr_2 = 0
639 },
640 },
641 },
642 // B.
643 {
644 .frame = 0,
645 .n_obj = 3,
646 .n_frames = 8,
647 .state = BTN_STATE_RELEASED,
648 .animations = &btn_animation_ab,
649 .sprites = &(ObjState[]){
650 {
651 .id = 0,
652 .x = SCREEN_WIDTH / 2 + 32 + 20,
653 .y = SCREEN_HEIGHT / 2 + 32,
654 .data = &gba_btn_b_data,
655 .n_tiles = 4,
656 .frames = 1,
657 .obj_attr_0 = OBJ_SHAPE_SQUARE,
658 .obj_attr_1 = OBJ_SIZE_MID,
659 .obj_attr_2 = 0
660 },
661 {
662 .id = 0,
663 .x = SCREEN_WIDTH / 2 + 32 + 20,
664 .y = SCREEN_HEIGHT / 2 + 32 + 8,
665 .data = &gba_btn_ab_shadow_data,
666 .n_tiles = 2,
667 .frames = 1,
668 .obj_attr_0 = OBJ_SHAPE_WIDE,
669 .obj_attr_1 = OBJ_SIZE_SMALL,
670 .obj_attr_2 = 0
671 },
672 {
673 .id = 0,
674 .x = SCREEN_WIDTH / 2 + 32 - 8 + 20,
675 .y = SCREEN_HEIGHT / 2 + 33 + 8 - 16,
676 .data = &gba_btn_fx_ab,
677 .n_tiles = 16,
678 .frames = 3,
679 .obj_attr_0 = OBJ_SHAPE_SQUARE,
680 .obj_attr_1 = OBJ_SIZE_BIG,
681 .obj_attr_2 = 0
682 },
683 },
684 },
685 // START.
686 {
687 .frame = 0,
688 .n_obj = 3,
689 .n_frames = 8,
690 .state = BTN_STATE_RELEASED,
691 .animations = &btn_animation_startselect,
692 .sprites = &(ObjState[]){
693 {
694 .id = 0,
695 .x = SCREEN_WIDTH / 2 - 10 + 26 - 14,
696 .y = SCREEN_HEIGHT / 2 + 40 + 10,
697 .data = &gba_btn_startselect_data,
698 .n_tiles = 2,
699 .frames = 2,
700 .obj_attr_0 = OBJ_SHAPE_WIDE,
701 .obj_attr_1 = OBJ_SIZE_SMALL,
702 .obj_attr_2 = 0
703 },
704 {
705 .id = 0,
706 .x = SCREEN_WIDTH / 2 - 18 + 26 - 14,
707 .y = SCREEN_HEIGHT / 2 + 46 + 10,
708 .data = &gba_btn_start_text,
709 .n_tiles = 4,
710 .frames = 1,
711 .obj_attr_0 = OBJ_SHAPE_WIDE,
712 .obj_attr_1 = OBJ_SIZE_MID,
713 .obj_attr_2 = 0
714 },
715 {
716 .id = 0,
717 .x = SCREEN_WIDTH / 2 - 19 + 26 - 14,
718 .y = SCREEN_HEIGHT / 2 + 37 + 10,
719 .data = &gba_btn_fx_startselect,
720 .n_tiles = 4,
721 .frames = 4,
722 .obj_attr_0 = OBJ_SHAPE_WIDE,
723 .obj_attr_1 = OBJ_SIZE_MID,
724 .obj_attr_2 = 0
725 },
726 },
727 },
728 // SELECT.
729 {
730 .frame = 0,
731 .n_obj = 3,
732 .n_frames = 8,
733 .state = BTN_STATE_RELEASED,
734 .animations = &btn_animation_startselect,
735 .sprites = &(ObjState[]){
736 {
737 .id = 0,
738 .x = SCREEN_WIDTH / 2 - 10 - 14,
739 .y = SCREEN_HEIGHT / 2 + 40 + 10,
740 .data = &gba_btn_startselect_data,
741 .n_tiles = 2,
742 .frames = 2,
743 .obj_attr_0 = OBJ_SHAPE_WIDE,
744 .obj_attr_1 = OBJ_SIZE_SMALL,
745 .obj_attr_2 = 0
746 },
747 {
748 .id = 0,
749 .x = SCREEN_WIDTH / 2 - 18 - 14,
750 .y = SCREEN_HEIGHT / 2 + 46 + 10,
751 .data = &gba_btn_select_text,
752 .n_tiles = 4,
753 .frames = 1,
754 .obj_attr_0 = OBJ_SHAPE_WIDE,
755 .obj_attr_1 = OBJ_SIZE_MID,
756 .obj_attr_2 = 0
757 },
758 {
759 .id = 0,
760 .x = SCREEN_WIDTH / 2 - 19 - 14,
761 .y = SCREEN_HEIGHT / 2 + 37 + 10,
762 .data = &gba_btn_fx_startselect,
763 .n_tiles = 4,
764 .frames = 4,
765 .obj_attr_0 = OBJ_SHAPE_WIDE,
766 .obj_attr_1 = OBJ_SIZE_MID,
767 .obj_attr_2 = 0
768 },
769 },
770 },
771};
772
773static inline void
774init_button_sprites(void) {
775 for (size_t i = 0; i < sizeof(buttons) / sizeof(ButtonSprite); ++i) {
776 init_button_sprite(&buttons[i]);
777 }
778}
779
780static inline void
781update_button_sprites(void) {
782 if (key_pressed(KEY_DOWN)) {
783 buttons[0].state = BTN_STATE_PRESSED;
784 } else if (key_hold(KEY_DOWN)) {
785 buttons[0].state = BTN_STATE_HOLD;
786 } else {
787 buttons[0].state = BTN_STATE_RELEASED;
788 }
789 if (key_pressed(KEY_UP)) {
790 buttons[1].state = BTN_STATE_PRESSED;
791 } else if (key_hold(KEY_UP)) {
792 buttons[1].state = BTN_STATE_HOLD;
793 } else {
794 buttons[1].state = BTN_STATE_RELEASED;
795 }
796 if (key_pressed(KEY_LEFT)) {
797 buttons[2].state = BTN_STATE_PRESSED;
798 } else if (key_hold(KEY_LEFT)) {
799 buttons[2].state = BTN_STATE_HOLD;
800 } else {
801 buttons[2].state = BTN_STATE_RELEASED;
802 }
803 if (key_pressed(KEY_RIGHT)) {
804 buttons[3].state = BTN_STATE_PRESSED;
805 } else if (key_hold(KEY_RIGHT)) {
806 buttons[3].state = BTN_STATE_HOLD;
807 } else {
808 buttons[3].state = BTN_STATE_RELEASED;
809 }
810 if (key_pressed(KEY_L)) {
811 buttons[4].state = BTN_STATE_PRESSED;
812 } else if (key_hold(KEY_L)) {
813 buttons[4].state = BTN_STATE_HOLD;
814 } else {
815 buttons[4].state = BTN_STATE_RELEASED;
816 }
817 if (key_pressed(KEY_R)) {
818 buttons[5].state = BTN_STATE_PRESSED;
819 } else if (key_hold(KEY_R)) {
820 buttons[5].state = BTN_STATE_HOLD;
821 } else {
822 buttons[5].state = BTN_STATE_RELEASED;
823 }
824 if (key_pressed(KEY_A)) {
825 buttons[6].state = BTN_STATE_PRESSED;
826 } else if (key_hold(KEY_A)) {
827 buttons[6].state = BTN_STATE_HOLD;
828 } else {
829 buttons[6].state = BTN_STATE_RELEASED;
830 }
831 if (key_pressed(KEY_B)) {
832 buttons[7].state = BTN_STATE_PRESSED;
833 } else if (key_hold(KEY_B)) {
834 buttons[7].state = BTN_STATE_HOLD;
835 } else {
836 buttons[7].state = BTN_STATE_RELEASED;
837 }
838 if (key_pressed(KEY_START)) {
839 buttons[8].state = BTN_STATE_PRESSED;
840 } else if (key_hold(KEY_START)) {
841 buttons[8].state = BTN_STATE_HOLD;
842 } else {
843 buttons[8].state = BTN_STATE_RELEASED;
844 }
845 if (key_pressed(KEY_SELECT)) {
846 buttons[9].state = BTN_STATE_PRESSED;
847 } else if (key_hold(KEY_SELECT)) {
848 buttons[9].state = BTN_STATE_HOLD;
849 } else {
850 buttons[9].state = BTN_STATE_RELEASED;
851 }
852
853 for (size_t i = 0; i < sizeof(buttons) / sizeof(ButtonSprite); ++i) {
854 button_tick(&buttons[i]);
855 }
856}
diff --git a/src/main.c b/src/main.c
index 3002f7e..b8f2334 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,677 +1,17 @@
1#include <string.h> 1#include <string.h>
2 2
3#include "shorthand.h" 3#include "common.h"
4#include "bd-font.c"
5#include "gba-buttons.c" 4#include "gba-buttons.c"
6#include "background-tiles.c" 5#include "background-tiles.c"
7 6#include "sprites.h"
8//
9// Memory sections.
10//
11
12// Defines for the different memory sections in the GBA.
13#define MEM_SROM 0x00000000
14#define MEM_EW 0x02000000
15#define MEM_IW 0x03000000
16#define MEM_IO 0x04000000
17#define MEM_PAL 0x05000000
18#define MEM_VRAM 0x06000000
19#define MEM_OAM 0x07000000
20#define MEM_PAK 0x08000000
21#define MEM_CART 0x0E000000
22
23//
24// Display modes.
25//
26
27// Display registers.
28#define DISP_CTRL *((vu32*)(MEM_IO + 0x0000))
29#define DISP_STATUS *((vu16*)(MEM_IO + 0x0004))
30#define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006))
31
32// The first three bits in the DISP_CTRL are used to control the video mode.
33#define DISP_MODE_0 0x0000
34#define DISP_MODE_1 0x0001
35#define DISP_MODE_2 0x0002
36#define DISP_MODE_3 0x0003
37#define DISP_MODE_4 0x0004
38#define DISP_MODE_5 0x0005
39#define DISP_GB (1 << 3)
40#define DISP_PAGE (1 << 4)
41#define DISP_OAM_HBLANK (1 << 5)
42#define DISP_OBJ_1D (1 << 6)
43#define DISP_BLANK (1 << 7)
44#define DISP_BG_0 (1 << 8)
45#define DISP_BG_1 (1 << 9)
46#define DISP_BG_2 (1 << 10)
47#define DISP_BG_3 (1 << 11)
48#define DISP_OBJ (1 << 12)
49#define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D
50
51// Registers to control of BG layers.
52#define BG_CTRL_0 *((vu16*)(0x04000008 + 0x0002 * 0))
53#define BG_CTRL_1 *((vu16*)(0x04000008 + 0x0002 * 1))
54#define BG_CTRL_2 *((vu16*)(0x04000008 + 0x0002 * 2))
55#define BG_CTRL_3 *((vu16*)(0x04000008 + 0x0002 * 3))
56
57// Bits to control the background.
58#define BG_PRIORITY_0 0x0
59#define BG_PRIORITY_1 0x1
60#define BG_PRIORITY_2 0x2
61#define BG_PRIORITY_3 0x3
62#define BG_CHARBLOCK(N) ((N) << 2)
63#define BG_MOSAIC (1 << 6)
64#define BG_HIGH_COLOR (1 << 7)
65#define BG_SCREENBLOCK(N) ((N) << 8)
66#define BG_AFFINE (1 << 0xD)
67#define BG_SIZE(N) ((N) << 0xE)
68
69// BG registers for horizontal displacement.
70#define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0))
71#define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1))
72#define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2))
73#define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3))
74
75// BG registers for vertical displacement.
76#define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0))
77#define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1))
78#define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2))
79#define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3))
80
81// Screen settings.
82#define SCREEN_WIDTH 240
83#define SCREEN_HEIGHT 160
84
85// The GBA in mode 3 expects rbg15 colors in the VRAM, where each component
86// (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0).
87typedef u16 Color;
88
89//
90// Tile memory access.
91//
92
93// NOTE: Only defining 4bpp tiles for now.
94typedef struct Tile {
95 u32 data[8];
96} Tile;
97
98typedef Tile TileBlock[512];
99#define TILE_MEM ((TileBlock*) MEM_VRAM)
100
101typedef u16 ScreenBlock[1024];
102#define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM)
103
104// We can treat the screen as a HxW matrix. With the following macro we can
105// write a pixel to the screen at the (x, y) position using:
106//
107// FRAMEBUFFER[y][x] = color;
108//
109typedef Color Scanline[SCREEN_WIDTH];
110#define FRAMEBUFFER ((Scanline*)MEM_VRAM)
111#define SCREEN_BUFFER ((u16*) MEM_VRAM)
112#define PAL_BUFFER_BG ((u16*) MEM_PAL)
113#define PAL_BUFFER_SPRITES ((u16*) 0x05000200)
114
115//
116// Colors.
117//
118
119static inline Color
120rgb15(u32 red, u32 green, u32 blue ) {
121 return (blue << 10) | (green << 5) | red;
122}
123
124#define COLOR_RED rgb15(31, 0, 12)
125#define COLOR_BLUE rgb15(2, 15, 30)
126#define COLOR_CYAN rgb15(0, 30, 30)
127#define COLOR_GREY rgb15(4, 4, 4)
128#define COLOR_BLACK rgb15(0, 0, 0)
129#define COLOR_WHITE rgb15(28, 28, 28)
130
131//
132// Sprites.
133//
134
135// Using macros instead of aligned structs for setting up OBJ attributes and
136// affine parameters.
137// TODO: Benchmark if this would be slower or the same that TONC's
138// implementation.
139#define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N)))
140#define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N)))
141#define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N)))
142#define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N)))
143#define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N)))
144#define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N)))
145#define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N)))
146
147// OBJ_ATTR_0 parameters
148#define OBJ_Y_COORD(N) ((N) & 0xFF)
149#define OBJ_NORMAL (0x00 << 0x8)
150#define OBJ_AFFINE (0x01 << 0x8)
151#define OBJ_HIDDEN (0x02 << 0x8)
152#define OBJ_AFFINE_2X (0x03 << 0x8)
153#define OBJ_ALPHA_BLEND (0x01 << 0xA)
154#define OBJ_WINDOW (0x02 << 0xA)
155#define OBJ_SHAPE_SQUARE (0x00 << 0xE)
156#define OBJ_SHAPE_WIDE (0x01 << 0xE)
157#define OBJ_SHAPE_TALL (0x02 << 0xE)
158
159// OBJ_ATTR_1 parameters
160#define OBJ_X_COORD(N) ((N) & 0x1FF)
161#define OBJ_AFFINE_IDX(N) ((N) << 0x9)
162#define OBJ_H_FLIP (0x01 << 0xC)
163#define OBJ_V_FLIP (0x01 << 0xD)
164#define OBJ_SIZE_SMALL (0x00 << 0xE)
165#define OBJ_SIZE_MID (0x01 << 0xE)
166#define OBJ_SIZE_BIG (0x02 << 0xE)
167#define OBJ_SIZE_HUGE (0x03 << 0xE)
168
169// OBJ_ATTR_2 parameters
170#define OBJ_TILE_INDEX(N) ((N) & 0x3FF)
171#define OBJ_PRIORITY(N) ((N) << 0xA)
172#define OBJ_PAL_BANK(N) ((N) << 0xC)
173
174// Using bd-font, an 8x8 bitmap font.
175static void
176put_char(int x, int y, Color clr, u8 chr) {
177 for (size_t i = 0; i < 8; ++i) {
178 for (size_t j = 0; j < 8; ++j) {
179 if ((font[chr][i] >> (7 - j)) & 0x1) {
180 FRAMEBUFFER[y + i][x + j] = clr;
181 }
182 }
183 }
184}
185
186static void
187put_text(int x, int y, Color clr, char *msg) {
188 int count = 0;
189 while (*msg) {
190 put_char(x + count, y, clr, *msg++);
191 count += 8;
192 }
193}
194
195// Draws a line with the given color between (x0,y0) and (x1,y1) using the
196// Bresenham's line drawing algorithm using exclusively integer arithmetic.
197static void
198draw_line(int x0, int y0, int x1, int y1, Color clr) {
199 // Pointer to the initial position of the screen buffer where we will start
200 // writing our data.
201 vu16 *destination = (u16*)(SCREEN_BUFFER + y0 * SCREEN_WIDTH + x0);
202
203 // Adjust the step direction and calculate deltas.
204 int x_step;
205 int y_step;
206 int dx;
207 int dy;
208 if (x0 > x1) {
209 x_step = -1;
210 dx = x0 - x1;
211 } else {
212 x_step = 1;
213 dx = x1 - x0;
214 }
215 if (y0 > y1) {
216 y_step = -SCREEN_WIDTH;
217 dy = y0 - y1;
218 } else {
219 y_step = +SCREEN_WIDTH;
220 dy = y1 - y0;
221 }
222
223 if(dy == 0) {
224 // Horizontal line.
225 for(int i = 0; i <= dx; i++) {
226 destination[i * x_step] = clr;
227 }
228 } else if(dx == 0) {
229 // Vertical line.
230 for(int i = 0; i <= dy; i++) {
231 destination[i * y_step] = clr;
232 }
233 } else if (dx >= dy){
234 // Positive slope.
235 int diff = 2 * dy - dx;
236 for (int i = 0; i <= dx; ++i) {
237 *destination = clr;
238 if (diff >= 0) {
239 destination += y_step;
240 diff -= 2 * dx;
241 }
242 destination += x_step;
243 diff += 2 * dy;
244 }
245 } else {
246 // Negative slope.
247 int diff = 2 * dx - dy;
248 for (int i = 0; i <= dy; ++i) {
249 *destination = clr;
250 if (diff >= 0) {
251 destination += x_step;
252 diff -= 2 * dy;
253 }
254 destination += y_step;
255 diff += 2 * dx;
256 }
257 }
258}
259
260static inline void
261draw_rect(int x0, int y0, int x1, int y1, Color clr) {
262 if (x0 > x1) {
263 int tmp = x0;
264 x0 = x1;
265 x1 = tmp;
266 }
267 if (y0 > y1) {
268 int tmp = y0;
269 y0 = y1;
270 y1 = tmp;
271 }
272 int dx = x1 - x0;
273 int dy = y1 - y0;
274 for (int i = 0; i <= dx; ++i) {
275 int x = x0 + i;
276 FRAMEBUFFER[y0][x] = clr;
277 FRAMEBUFFER[y1][x] = clr;
278 }
279 for (int j = 0; j <= dy; ++j) {
280 int y = y0 + j;
281 FRAMEBUFFER[y][x0] = clr;
282 FRAMEBUFFER[y][x1] = clr;
283 }
284}
285
286static inline void
287draw_fill_rect(int x0, int y0, int x1, int y1, Color clr) {
288 if (x0 > x1) {
289 int tmp = x0;
290 x0 = x1;
291 x1 = tmp;
292 }
293 if (y0 > y1) {
294 int tmp = y0;
295 y0 = y1;
296 y1 = tmp;
297 }
298 int dx = x1 - x0;
299 int dy = y1 - y0;
300 for (int i = 0; i <= dx; ++i) {
301 for (int j = 0; j <= dy; ++j) {
302 int x = x0 + i;
303 int y = y0 + j;
304 FRAMEBUFFER[y][x] = clr;
305 }
306 }
307}
308
309static inline void
310wait_vsync(void) {
311 while(DISP_VCOUNT >= 160);
312 while(DISP_VCOUNT < 160);
313}
314 7
315// 8//
316// Main functions. 9// Main functions.
317// 10//
318 11
319// In Mode4 the buffer is of 8 bytes per pixel instead of 16. We can't write the 12// TODO: Cleanup OBJ/OAM memory copying and access.
320// color directly, instead the color is stored in the palette memory at
321// `MEM_PAL`. Note that in this mode MEM_PAL[0] is the background color. This
322// plotter takes an index to a color stored in MEM_PAL[col_index]. Because the
323// GBA needs to meet memory alignment requirements, we can't write a u8 into
324// memory, instead we need to read a u16 word, mask and or the corresponding
325// bits and wave the updated u16.
326static void
327put_pixel_m4(int x, int y, u8 col_index, vu16 *buffer) {
328 int buffer_index = (y * SCREEN_WIDTH + x) / 2;
329 vu16 *destination = &buffer[buffer_index];
330 // Odd pixels will go to the top 8 bits of the destination. Even pixels to
331 // the lower 8 bits.
332 int odd = x & 0x1;
333 if(odd) {
334 *destination= (*destination & 0xFF) | (col_index << 8);
335 } else {
336 *destination= (*destination & ~0xFF) | col_index;
337 }
338}
339
340static void
341draw_fill_rect_m4(int x0, int y0, int x1, int y1, u8 col_index, vu16 *buffer) {
342 int ix, iy;
343 for(iy = y0; iy < y1; iy++) {
344 for(ix = x0; ix < x1; ix++) {
345 put_pixel_m4(ix, iy, col_index, buffer);
346 }
347 }
348}
349
350static inline void
351flip_page(void) {
352 DISP_CTRL ^= DISP_PAGE;
353}
354
355#define SCREEN_PAGE_1 ((vu16*) MEM_VRAM)
356#define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000))
357
358//
359// Profiling.
360//
361
362#define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0))
363#define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1))
364#define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2))
365#define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3))
366#define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0))
367#define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1))
368#define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2))
369#define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3))
370
371// Timer control bits.
372#define TIMER_CTRL_FREQ_0 0
373#define TIMER_CTRL_FREQ_1 1
374#define TIMER_CTRL_FREQ_2 2
375#define TIMER_CTRL_FREQ_3 3
376#define TIMER_CTRL_CASCADE (1 << 2)
377#define TIMER_CTRL_IRQ (1 << 6)
378#define TIMER_CTRL_ENABLE (1 << 7)
379
380// We use timers 2 and 3 to count the number of cycles since the profile_start
381// functions is called. Don't use if the code we are trying to profile make use
382// of these timers.
383static inline
384void profile_start(void) {
385 TIMER_DATA_2 = 0;
386 TIMER_DATA_3 = 0;
387 TIMER_CTRL_2 = 0;
388 TIMER_CTRL_3 = 0;
389 TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE;
390 TIMER_CTRL_2 = TIMER_CTRL_ENABLE;
391}
392
393static inline
394u32 profile_stop(void) {
395 TIMER_CTRL_2 = 0;
396 return (TIMER_DATA_3 << 16) | TIMER_DATA_2;
397}
398
399//
400// Input handling.
401//
402
403// Memory address for key input register
404#define KEY_INPUTS *((vu16*) 0x04000130)
405
406// Alias for key pressing bits.
407#define KEY_A (1 << 0)
408#define KEY_B (1 << 1)
409#define KEY_SELECT (1 << 2)
410#define KEY_START (1 << 3)
411#define KEY_RIGHT (1 << 4)
412#define KEY_LEFT (1 << 5)
413#define KEY_UP (1 << 6)
414#define KEY_DOWN (1 << 7)
415#define KEY_R (1 << 8)
416#define KEY_L (1 << 9)
417
418#define KEY_MASK 0x03FF
419
420// Saving the previous and current key states as globals for now.
421static u16 key_curr = 0;
422static u16 key_prev = 0;
423
424static inline void
425poll_keys(void) {
426 key_prev = key_curr;
427 key_curr = ~KEY_INPUTS & KEY_MASK;
428}
429
430// Returns true if the given key has been pressed at time of calling and was not
431// pressed since the previous call. For example, if a key is being held, this
432// function will return `true` only on the frame where the key initially
433// activated.
434static inline u32
435key_pressed(u32 key) {
436 return (key_curr & key) & ~(key_prev & key);
437}
438
439// Check if the given key is pressed and has been since at least one frame.
440static inline u32
441key_hold(u32 key) {
442 return (key_curr & key) & key_prev & key;
443}
444
445// Check if the given key/button is currently pressed.
446#define KEY_PRESSED(key) (~(KEY_INPUTS) & key)
447
448void
449draw_logo(void) {
450 int side = 60;
451 int line = 35;
452 int height = side * 0.5;
453 int x = SCREEN_WIDTH / 2 - height / 2;
454 int y = SCREEN_HEIGHT / 2;
455
456 // Draw red triangle.
457 draw_line(x + height - 1, y - side / 2, x, y - 1, COLOR_RED);
458 draw_line(x + height - 1, y + side / 2, x, y + 1, COLOR_RED);
459 draw_line(x + height - 1, y - side / 2 + 1, x, y, COLOR_RED);
460 draw_line(x + height - 1, y + side / 2 - 1, x, y, COLOR_RED);
461
462 // Draw white triangle.
463 draw_line(x, y - side / 2, x, y + side / 2, COLOR_WHITE);
464 draw_line(x + 1, y - side / 2, x + height, y - 1, COLOR_WHITE);
465 draw_line(x + 1, y + side / 2, x + height, y + 1, COLOR_WHITE);
466
467 // Draw white line at triangle tip.
468 draw_line(x + height, y - side / 2, x + height, y + side / 2, COLOR_WHITE);
469 draw_line(x + height + 1, y - side / 2, x + height + 1, y + side / 2, COLOR_WHITE);
470
471 // Double triangle line.
472 draw_line(x - 1, y - side / 2, x - 1, y + side / 2, COLOR_WHITE);
473 draw_line(x + 1, y - side / 2 + 1, x + height, y, COLOR_WHITE);
474 draw_line(x + 1, y + side / 2 - 1, x + height, y, COLOR_WHITE);
475
476 // Draw white lines.
477 draw_line(x - line, y, x, y, COLOR_WHITE);
478 draw_line(x + height, y, x + height + line, y, COLOR_WHITE);
479 draw_line(x - line, y + 1, x, y + 1, COLOR_WHITE);
480 draw_line(x + height, y + 1, x + height + line, y + 1, COLOR_WHITE);
481}
482
483void
484copy_font_to_tile_memory(Tile *tile) {
485 // Hex to bits translation table.
486 const u32 conversion_u32[16] = {
487 0x00000000, 0x00001000, 0x00000100, 0x00001100,
488 0x00000010, 0x00001010, 0x00000110, 0x00001110,
489 0x00000001, 0x00001001, 0x00000101, 0x00001101,
490 0x00000011, 0x00001011, 0x00000111, 0x00001111,
491 };
492 for (size_t i = 0; i < 250; ++i) {
493 for (size_t j = 0; j < 8; ++j) {
494 u8 row = font[i][j];
495 u32 tile_idx = 0x00000000;
496 tile_idx = conversion_u32[row & 0xF] << 16;
497 tile_idx |= conversion_u32[(row >> 4) & 0xF];
498 (tile + i)->data[j] = tile_idx;
499 }
500 }
501}
502
503
504u32
505unpack_1bb(u8 hex) {
506 const u32 conversion_u32[16] = {
507 0x00000000, 0x00000001, 0x00000010, 0x00000011,
508 0x00000100, 0x00000101, 0x00000110, 0x00000111,
509 0x00001000, 0x00001001, 0x00001010, 0x00001011,
510 0x00001100, 0x00001101, 0x00001110, 0x00001111,
511 };
512 u8 low = hex & 0xF;
513 u8 high = (hex >> 4) & 0xF;
514 return (conversion_u32[high] << 16) | conversion_u32[low];
515}
516
517typedef struct Sprite {
518 // A unique sprite identifier.
519 size_t id;
520 // The number of tiles for a single sprite frame.
521 size_t n_tiles;
522 // The starting tile of this sprite.
523 size_t tile_start;
524 // The associated palette bank for this sprite.
525 size_t pal_bank;
526} Sprite;
527
528typedef struct ButtonSprite {
529 int id;
530 int x;
531 int y;
532 int frame;
533 BtnState state;
534} ButtonSprite;
535
536typedef struct MultiSprite {
537 ObjState *sprites;
538 AnimationEntry **animations;
539 size_t frame;
540 size_t n_obj;
541 size_t n_frames;
542 BtnState state;
543} MultiSprite;
544
545#define NUM_SPRITES 128
546
547Sprite sprites[NUM_SPRITES];
548
549// Keeping track of unique sprites and current sprite memory pointer using
550// global singletons.
551size_t sprite_counter = 0;
552size_t sprite_tile_counter = 0;
553u32 *sprite_memory = NULL;
554
555// Loads the sprite data into video memory and initialize the Sprite structure.
556size_t
557load_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) {
558 memcpy(sprite_memory, sprite_data, 8 * n_tiles * n_frames * sizeof(u32));
559 sprite_memory += 8 * n_tiles * n_frames;
560 Sprite sprite = {
561 .id = sprite_counter,
562 .n_tiles = n_tiles,
563 .tile_start = sprite_tile_counter,
564 };
565 sprite_tile_counter += n_tiles * n_frames;
566 sprites[sprite_counter] = sprite;
567 return sprite_counter++;
568}
569
570size_t
571load_packed_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) {
572 size_t counter = 0;
573 for (size_t i = 0; i < 8 * n_tiles * n_frames / 4; ++i) {
574 u32 hex = sprite_data[i];
575 sprite_memory[counter++] = unpack_1bb((hex >> 24) & 0xFF);
576 sprite_memory[counter++] = unpack_1bb((hex >> 16) & 0xFF);
577 sprite_memory[counter++] = unpack_1bb((hex >> 8) & 0xFF);
578 sprite_memory[counter++] = unpack_1bb((hex) & 0xFF);
579 }
580 sprite_memory += 8 * n_tiles * n_frames;
581 Sprite sprite = {
582 .id = sprite_counter,
583 .n_tiles = n_tiles,
584 .tile_start = sprite_tile_counter,
585 };
586 sprite_tile_counter += n_tiles * n_frames;
587 sprites[sprite_counter] = sprite;
588 return sprite_counter++;
589}
590
591void
592init_button_sprite(MultiSprite *btn) {
593 for (size_t i = 0; i < btn->n_obj; ++i) {
594 btn->sprites[i].id = load_packed_sprite_data(
595 btn->sprites[i].data,
596 btn->sprites[i].n_tiles,
597 btn->sprites[i].frames);
598 btn->sprites[i].base_tile = sprites[btn->sprites[i].id].tile_start;
599 }
600}
601
602void
603button_tick(MultiSprite *btn) {
604 // Nothing to do here.
605 if (btn->state == BTN_STATE_IDLE) {
606 return;
607 }
608
609 // Reset animation state.
610 if (btn->state == BTN_STATE_PRESSED && btn->frame != 0) {
611 btn->frame = 0;
612 }
613
614 // Continue the animation.
615 if (btn->state == BTN_STATE_HOLD || btn->state == BTN_STATE_PRESSED ) {
616 if(btn->frame < btn->n_frames - 1) {
617 btn->frame++;
618 }
619 }
620
621 // Finish the animation and return to idle.
622 if (btn->state == BTN_STATE_RELEASED) {
623 if (btn->frame > 0 && btn->frame < btn->n_frames - 1) {
624 btn->frame++;
625 } else {
626 btn->frame = 0;
627 btn->state = BTN_STATE_IDLE;
628 }
629 }
630 for (size_t i = 0; i < btn->n_obj; ++i) {
631 AnimationEntry anim_frame = btn->animations[i][btn->frame];
632 int x = btn->sprites[i].x + anim_frame.x_offset;
633 int y = btn->sprites[i].y + anim_frame.y_offset;
634 int base_tile = btn->sprites[i].base_tile + anim_frame.tile_offset;
635
636 // Clear the previous x/y coordinate and base tiles.
637 btn->sprites[i].obj_attr_0 &= ~0xFF;
638 btn->sprites[i].obj_attr_1 &= ~0x1FF;
639 btn->sprites[i].obj_attr_2 &= ~0x3FF;
640
641 // Update x/y/tile and hidden state from the animations.
642 btn->sprites[i].obj_attr_0 |= OBJ_Y_COORD(y);
643 btn->sprites[i].obj_attr_1 |= OBJ_X_COORD(x);
644 btn->sprites[i].obj_attr_2 |= base_tile;
645 if (anim_frame.hidden) {
646 btn->sprites[i].obj_attr_0 |= OBJ_HIDDEN;
647 } else {
648 btn->sprites[i].obj_attr_0 &= ~OBJ_HIDDEN;
649 }
650
651 // Update OBJ attributes.
652 OBJ_ATTR_0(btn->sprites[i].id) = btn->sprites[i].obj_attr_0;
653 OBJ_ATTR_1(btn->sprites[i].id) = btn->sprites[i].obj_attr_1;
654 OBJ_ATTR_2(btn->sprites[i].id) = btn->sprites[i].obj_attr_2;
655 }
656}
657 13
658int main(void) { 14int main(void) {
659
660 // Add colors to the sprite color palette. Tiles with color number 0 are
661 // treated as transparent.
662 for (size_t i = 0; i < 16; ++i) {
663 PAL_BUFFER_SPRITES[i] = COLOR_WHITE;
664 }
665
666 // Initialize all attributes by disabling rendering. If we don't do this,
667 // glitches may appear.
668 for (size_t i = 0; i < 128; ++i) {
669 OBJ_ATTR_0(i) = (1 << 9);
670 }
671
672 sprite_tile_counter = 0;
673 sprite_memory = &TILE_MEM[4][sprite_tile_counter];
674
675 // Load background palette. 15 // Load background palette.
676 memcpy(&PAL_BUFFER_BG[0], &bg_palette, 512); 16 memcpy(&PAL_BUFFER_BG[0], &bg_palette, 512);
677 memcpy(&TILE_MEM[0][0], bg_data, 3168); 17 memcpy(&TILE_MEM[0][0], bg_data, 3168);
@@ -687,445 +27,10 @@ int main(void) {
687 // sequential. 27 // sequential.
688 DISP_CTRL = DISP_ENABLE_SPRITES | DISP_MODE_0 | DISP_BG_0; 28 DISP_CTRL = DISP_ENABLE_SPRITES | DISP_MODE_0 | DISP_BG_0;
689 29
690 // Initialize the A/B button sprites. 30 // Initialize sprite button overlay.
691 int buttons_x = SCREEN_WIDTH / 2; 31 init_sprite_pal(0, COLOR_WHITE);
692 int buttons_y = SCREEN_HEIGHT / 2; 32 init_sprites(0);
693 33 init_button_sprites();
694 MultiSprite buttons[] = {
695 // DOWN.
696 {
697 .frame = 0,
698 .n_obj = 3,
699 .n_frames = 8,
700 .state = BTN_STATE_RELEASED,
701 .animations = &btn_animation,
702 .sprites = &(ObjState[]){
703 {
704 .id = 0,
705 .x = buttons_x - 64 - 16,
706 .y = buttons_y + 29,
707 .data = &gba_btn_updown_data,
708 .n_tiles = 4,
709 .frames = 1,
710 .obj_attr_0 = 0,
711 .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID,
712 .obj_attr_2 = 0
713 },
714 {
715 .id = 0,
716 .x = buttons_x - 64 - 16,
717 .y = buttons_y + 29 + 11,
718 .data = &gba_btn_down_shadow_data,
719 .n_tiles = 2,
720 .frames = 1,
721 .obj_attr_0 = OBJ_SHAPE_WIDE,
722 .obj_attr_1 = OBJ_SIZE_SMALL,
723 .obj_attr_2 = 0
724 },
725 {
726 .id = 0,
727 .x = buttons_x - 64 - 16 - 8,
728 .y = buttons_y + 29 + 17,
729 .data = &gba_btn_fx_downup,
730 .n_tiles = 4,
731 .frames = 4,
732 .obj_attr_0 = OBJ_SHAPE_WIDE,
733 .obj_attr_1 = OBJ_SIZE_MID,
734 .obj_attr_2 = 0
735 },
736 },
737 },
738 // UP.
739 {
740 .frame = 0,
741 .n_obj = 3,
742 .n_frames = 8,
743 .state = BTN_STATE_RELEASED,
744 .animations = &btn_animation,
745 .sprites = &(ObjState[]){
746 {
747 .id = 0,
748 .x = buttons_x - 64 - 16,
749 .y = buttons_y + 32 - 18,
750 .data = &gba_btn_updown_data,
751 .n_tiles = 4,
752 .frames = 1,
753 .obj_attr_0 = 0,
754 .obj_attr_1 = OBJ_SIZE_MID,
755 .obj_attr_2 = 0
756 },
757 {
758 .id = 0,
759 .x = buttons_x - 64 - 16,
760 .y = buttons_y + 32 - 18 + 7,
761 .data = &gba_btn_up_shadow_data,
762 .n_tiles = 2,
763 .frames = 1,
764 .obj_attr_0 = OBJ_SHAPE_WIDE,
765 .obj_attr_1 = OBJ_SIZE_SMALL,
766 .obj_attr_2 = 0
767 },
768 {
769 .id = 0,
770 .x = buttons_x - 64 - 16 - 8,
771 .y = buttons_y + 32 - 18 - 7,
772 .data = &gba_btn_fx_downup,
773 .n_tiles = 4,
774 .frames = 4,
775 .obj_attr_0 = OBJ_SHAPE_WIDE,
776 .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID,
777 .obj_attr_2 = 0
778 },
779 },
780 },
781 // LEFT
782 {
783 .frame = 0,
784 .n_obj = 3,
785 .n_frames = 8,
786 .state = BTN_STATE_RELEASED,
787 .animations = &btn_animation,
788 .sprites = &(ObjState[]){
789 {
790 .id = 0,
791 .x = buttons_x - 64 - 16 - 10,
792 .y = buttons_y + 32 - 10,
793 .data = &gba_btn_leftright_data,
794 .n_tiles = 4,
795 .frames = 1,
796 .obj_attr_0 = 0,
797 .obj_attr_1 = OBJ_SIZE_MID,
798 .obj_attr_2 = 0
799 },
800 {
801 .id = 0,
802 .x = buttons_x - 64 - 16 - 10,
803 .y = buttons_y + 32 - 10 + 6,
804 .data = &gba_btn_leftright_shadow_data,
805 .n_tiles = 2,
806 .frames = 1,
807 .obj_attr_0 = OBJ_SHAPE_WIDE,
808 .obj_attr_1 = OBJ_SIZE_SMALL,
809 .obj_attr_2 = 0
810 },
811 {
812 .id = 0,
813 .x = buttons_x - 64 - 16 - 10 - 6,
814 .y = buttons_y + 32 - 10 - 8,
815 .data = &gba_btn_fx_leftright,
816 .n_tiles = 4,
817 .frames = 4,
818 .obj_attr_0 = OBJ_SHAPE_TALL,
819 .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID,
820 .obj_attr_2 = 0
821 },
822 },
823 },
824 // RIGHT.
825 {
826 .frame = 0,
827 .n_obj = 3,
828 .n_frames = 8,
829 .state = BTN_STATE_RELEASED,
830 .animations = &btn_animation,
831 .sprites = &(ObjState[]){
832 {
833 .id = 0,
834 .x = buttons_x - 64 - 16 + 11,
835 .y = buttons_y + 32 - 10,
836 .data = &gba_btn_leftright_data,
837 .n_tiles = 4,
838 .frames = 1,
839 .obj_attr_0 = 0,
840 .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID,
841 .obj_attr_2 = 0
842 },
843 {
844 .id = 0,
845 .x = buttons_x - 64 - 16 + 11,
846 .y = buttons_y + 32 - 10 + 6,
847 .data = &gba_btn_leftright_shadow_data,
848 .n_tiles = 2,
849 .frames = 1,
850 .obj_attr_0 = OBJ_SHAPE_WIDE,
851 .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_SMALL,
852 .obj_attr_2 = 0
853 },
854 {
855 .id = 0,
856 .x = buttons_x - 64 - 16 + 11 + 14,
857 .y = buttons_y + 32 - 10 - 8,
858 .data = &gba_btn_fx_leftright,
859 .n_tiles = 4,
860 .frames = 4,
861 .obj_attr_0 = OBJ_SHAPE_TALL,
862 .obj_attr_1 = OBJ_SIZE_MID,
863 .obj_attr_2 = 0
864 },
865 },
866 },
867 // L.
868 {
869 .frame = 0,
870 .n_obj = 3,
871 .n_frames = 8,
872 .state = BTN_STATE_RELEASED,
873 .animations = &btn_animation,
874 .sprites = &(ObjState[]){
875 {
876 .id = 0,
877 .x = buttons_x - 64 - 28,
878 .y = buttons_y - 32 - 20,
879 .data = &gba_btn_l_data,
880 .n_tiles = 2,
881 .frames = 1,
882 .obj_attr_0 = OBJ_SHAPE_WIDE,
883 .obj_attr_1 = OBJ_SIZE_SMALL,
884 .obj_attr_2 = 0
885 },
886 {
887 .id = 0,
888 .x = buttons_x - 64 - 28,
889 .y = buttons_y - 32 - 20 + 2,
890 .data = &gba_btn_lr_shadow_data,
891 .n_tiles = 2,
892 .frames = 1,
893 .obj_attr_0 = OBJ_SHAPE_WIDE,
894 .obj_attr_1 = OBJ_SIZE_SMALL,
895 .obj_attr_2 = 0
896 },
897 {
898 .id = 0,
899 .x = buttons_x - 64 - 28 - 12,
900 .y = buttons_y - 32 - 20 - 12,
901 .data = &gba_btn_fx_lr,
902 .n_tiles = 4,
903 .frames = 4,
904 .obj_attr_0 = OBJ_SHAPE_SQUARE,
905 .obj_attr_1 = OBJ_SIZE_MID,
906 .obj_attr_2 = 0
907 },
908 },
909 },
910 // R.
911 {
912 .frame = 0,
913 .n_obj = 3,
914 .n_frames = 8,
915 .state = BTN_STATE_RELEASED,
916 .animations = &btn_animation,
917 .sprites = &(ObjState[]){
918 {
919 .id = 0,
920 .x = buttons_x + 32 + 20 + 24,
921 .y = buttons_y - 32 - 20,
922 .data = &gba_btn_r_data,
923 .n_tiles = 2,
924 .frames = 1,
925 .obj_attr_0 = OBJ_SHAPE_WIDE,
926 .obj_attr_1 = OBJ_SIZE_SMALL,
927 .obj_attr_2 = 0
928 },
929 {
930 .id = 0,
931 .x = buttons_x + 32 + 20 + 24,
932 .y = buttons_y - 32 - 20 + 2,
933 .data = &gba_btn_lr_shadow_data,
934 .n_tiles = 2,
935 .frames = 1,
936 .obj_attr_0 = OBJ_SHAPE_WIDE,
937 .obj_attr_1 = OBJ_SIZE_SMALL,
938 .obj_attr_2 = 0
939 },
940 {
941 .id = 0,
942 .x = buttons_x + 32 + 20 + 12 + 24,
943 .y = buttons_y - 32 - 20 - 12,
944 .data = &gba_btn_fx_lr,
945 .n_tiles = 4,
946 .frames = 4,
947 .obj_attr_0 = OBJ_SHAPE_SQUARE,
948 .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID,
949 .obj_attr_2 = 0
950 },
951 },
952 },
953 // A.
954 {
955 .frame = 0,
956 .n_obj = 3,
957 .n_frames = 8,
958 .state = BTN_STATE_RELEASED,
959 .animations = &btn_animation_ab,
960 .sprites = &(ObjState[]){
961 {
962 .id = 0,
963 .x = buttons_x + 32 + 20 + 20,
964 .y = buttons_y + 32 - 16,
965 .data = &gba_btn_a_data,
966 .n_tiles = 4,
967 .frames = 1,
968 .obj_attr_0 = OBJ_SHAPE_SQUARE,
969 .obj_attr_1 = OBJ_SIZE_MID,
970 .obj_attr_2 = 0
971 },
972 {
973 .id = 0,
974 .x = buttons_x + 32 + 20 + 20,
975 .y = buttons_y + 32 - 16 + 8,
976 .data = &gba_btn_ab_shadow_data,
977 .n_tiles = 2,
978 .frames = 1,
979 .obj_attr_0 = OBJ_SHAPE_WIDE,
980 .obj_attr_1 = OBJ_SIZE_SMALL,
981 .obj_attr_2 = 0
982 },
983 {
984 .id = 0,
985 .x = buttons_x + 32 + 19 - 7 + 20,
986 .y = buttons_y + 32 - 23,
987 .data = &gba_btn_fx_ab,
988 .n_tiles = 16,
989 .frames = 3,
990 .obj_attr_0 = OBJ_SHAPE_SQUARE,
991 .obj_attr_1 = OBJ_SIZE_BIG,
992 .obj_attr_2 = 0
993 },
994 },
995 },
996 // B.
997 {
998 .frame = 0,
999 .n_obj = 3,
1000 .n_frames = 8,
1001 .state = BTN_STATE_RELEASED,
1002 .animations = &btn_animation_ab,
1003 .sprites = &(ObjState[]){
1004 {
1005 .id = 0,
1006 .x = buttons_x + 32 + 20,
1007 .y = buttons_y + 32,
1008 .data = &gba_btn_b_data,
1009 .n_tiles = 4,
1010 .frames = 1,
1011 .obj_attr_0 = OBJ_SHAPE_SQUARE,
1012 .obj_attr_1 = OBJ_SIZE_MID,
1013 .obj_attr_2 = 0
1014 },
1015 {
1016 .id = 0,
1017 .x = buttons_x + 32 + 20,
1018 .y = buttons_y + 32 + 8,
1019 .data = &gba_btn_ab_shadow_data,
1020 .n_tiles = 2,
1021 .frames = 1,
1022 .obj_attr_0 = OBJ_SHAPE_WIDE,
1023 .obj_attr_1 = OBJ_SIZE_SMALL,
1024 .obj_attr_2 = 0
1025 },
1026 {
1027 .id = 0,
1028 .x = buttons_x + 32 - 8 + 20,
1029 .y = buttons_y + 33 + 8 - 16,
1030 .data = &gba_btn_fx_ab,
1031 .n_tiles = 16,
1032 .frames = 3,
1033 .obj_attr_0 = OBJ_SHAPE_SQUARE,
1034 .obj_attr_1 = OBJ_SIZE_BIG,
1035 .obj_attr_2 = 0
1036 },
1037 },
1038 },
1039 // START.
1040 {
1041 .frame = 0,
1042 .n_obj = 3,
1043 .n_frames = 8,
1044 .state = BTN_STATE_RELEASED,
1045 .animations = &btn_animation_startselect,
1046 .sprites = &(ObjState[]){
1047 {
1048 .id = 0,
1049 .x = buttons_x - 10 - 14,
1050 .y = buttons_y + 40 + 10,
1051 .data = &gba_btn_startselect_data,
1052 .n_tiles = 2,
1053 .frames = 2,
1054 .obj_attr_0 = OBJ_SHAPE_WIDE,
1055 .obj_attr_1 = OBJ_SIZE_SMALL,
1056 .obj_attr_2 = 0
1057 },
1058 {
1059 .id = 0,
1060 .x = buttons_x - 18 - 14,
1061 .y = buttons_y + 46 + 10,
1062 .data = &gba_btn_start_text,
1063 .n_tiles = 4,
1064 .frames = 1,
1065 .obj_attr_0 = OBJ_SHAPE_WIDE,
1066 .obj_attr_1 = OBJ_SIZE_MID,
1067 .obj_attr_2 = 0
1068 },
1069 {
1070 .id = 0,
1071 .x = buttons_x - 19 - 14,
1072 .y = buttons_y + 37 + 10,
1073 .data = &gba_btn_fx_startselect,
1074 .n_tiles = 4,
1075 .frames = 4,
1076 .obj_attr_0 = OBJ_SHAPE_WIDE,
1077 .obj_attr_1 = OBJ_SIZE_MID,
1078 .obj_attr_2 = 0
1079 },
1080 },
1081 },
1082 // SELECT.
1083 {
1084 .frame = 0,
1085 .n_obj = 3,
1086 .n_frames = 8,
1087 .state = BTN_STATE_RELEASED,
1088 .animations = &btn_animation_startselect,
1089 .sprites = &(ObjState[]){
1090 {
1091 .id = 0,
1092 .x = buttons_x - 10 + 26 - 14,
1093 .y = buttons_y + 40 + 10,
1094 .data = &gba_btn_startselect_data,
1095 .n_tiles = 2,
1096 .frames = 2,
1097 .obj_attr_0 = OBJ_SHAPE_WIDE,
1098 .obj_attr_1 = OBJ_SIZE_SMALL,
1099 .obj_attr_2 = 0
1100 },
1101 {
1102 .id = 0,
1103 .x = buttons_x - 18 + 26 - 14,
1104 .y = buttons_y + 46 + 10,
1105 .data = &gba_btn_select_text,
1106 .n_tiles = 4,
1107 .frames = 1,
1108 .obj_attr_0 = OBJ_SHAPE_WIDE,
1109 .obj_attr_1 = OBJ_SIZE_MID,
1110 .obj_attr_2 = 0
1111 },
1112 {
1113 .id = 0,
1114 .x = buttons_x - 19 + 26 - 14,
1115 .y = buttons_y + 37 + 10,
1116 .data = &gba_btn_fx_startselect,
1117 .n_tiles = 4,
1118 .frames = 4,
1119 .obj_attr_0 = OBJ_SHAPE_WIDE,
1120 .obj_attr_1 = OBJ_SIZE_MID,
1121 .obj_attr_2 = 0
1122 },
1123 },
1124 },
1125 };
1126 for (size_t i = 0; i < sizeof(buttons) / sizeof(MultiSprite); ++i) {
1127 init_button_sprite(&buttons[i]);
1128 }
1129 34
1130 int frame_counter = 0; 35 int frame_counter = 0;
1131 int x = 0; 36 int x = 0;
@@ -1134,77 +39,6 @@ int main(void) {
1134 wait_vsync(); 39 wait_vsync();
1135 poll_keys(); 40 poll_keys();
1136 41
1137 if (key_pressed(KEY_DOWN)) {
1138 buttons[0].state = BTN_STATE_PRESSED;
1139 } else if (key_hold(KEY_DOWN)) {
1140 buttons[0].state = BTN_STATE_HOLD;
1141 } else {
1142 buttons[0].state = BTN_STATE_RELEASED;
1143 }
1144 if (key_pressed(KEY_UP)) {
1145 buttons[1].state = BTN_STATE_PRESSED;
1146 } else if (key_hold(KEY_UP)) {
1147 buttons[1].state = BTN_STATE_HOLD;
1148 } else {
1149 buttons[1].state = BTN_STATE_RELEASED;
1150 }
1151 if (key_pressed(KEY_LEFT)) {
1152 buttons[2].state = BTN_STATE_PRESSED;
1153 } else if (key_hold(KEY_LEFT)) {
1154 buttons[2].state = BTN_STATE_HOLD;
1155 } else {
1156 buttons[2].state = BTN_STATE_RELEASED;
1157 }
1158 if (key_pressed(KEY_RIGHT)) {
1159 buttons[3].state = BTN_STATE_PRESSED;
1160 } else if (key_hold(KEY_RIGHT)) {
1161 buttons[3].state = BTN_STATE_HOLD;
1162 } else {
1163 buttons[3].state = BTN_STATE_RELEASED;
1164 }
1165 if (key_pressed(KEY_L)) {
1166 buttons[4].state = BTN_STATE_PRESSED;
1167 } else if (key_hold(KEY_L)) {
1168 buttons[4].state = BTN_STATE_HOLD;
1169 } else {
1170 buttons[4].state = BTN_STATE_RELEASED;
1171 }
1172 if (key_pressed(KEY_R)) {
1173 buttons[5].state = BTN_STATE_PRESSED;
1174 } else if (key_hold(KEY_R)) {
1175 buttons[5].state = BTN_STATE_HOLD;
1176 } else {
1177 buttons[5].state = BTN_STATE_RELEASED;
1178 }
1179 if (key_pressed(KEY_A)) {
1180 buttons[6].state = BTN_STATE_PRESSED;
1181 } else if (key_hold(KEY_A)) {
1182 buttons[6].state = BTN_STATE_HOLD;
1183 } else {
1184 buttons[6].state = BTN_STATE_RELEASED;
1185 }
1186 if (key_pressed(KEY_B)) {
1187 buttons[7].state = BTN_STATE_PRESSED;
1188 } else if (key_hold(KEY_B)) {
1189 buttons[7].state = BTN_STATE_HOLD;
1190 } else {
1191 buttons[7].state = BTN_STATE_RELEASED;
1192 }
1193 if (key_pressed(KEY_START)) {
1194 buttons[8].state = BTN_STATE_PRESSED;
1195 } else if (key_hold(KEY_START)) {
1196 buttons[8].state = BTN_STATE_HOLD;
1197 } else {
1198 buttons[8].state = BTN_STATE_RELEASED;
1199 }
1200 if (key_pressed(KEY_SELECT)) {
1201 buttons[9].state = BTN_STATE_PRESSED;
1202 } else if (key_hold(KEY_SELECT)) {
1203 buttons[9].state = BTN_STATE_HOLD;
1204 } else {
1205 buttons[9].state = BTN_STATE_RELEASED;
1206 }
1207
1208 if (key_hold(KEY_DOWN)) { 42 if (key_hold(KEY_DOWN)) {
1209 y += 3; 43 y += 3;
1210 } 44 }
@@ -1222,9 +56,7 @@ int main(void) {
1222 BG_H_SCROLL_0 = x; 56 BG_H_SCROLL_0 = x;
1223 BG_V_SCROLL_0 = y; 57 BG_V_SCROLL_0 = y;
1224 58
1225 for (size_t i = 0; i < sizeof(buttons) / sizeof(MultiSprite); ++i) { 59 update_button_sprites();
1226 button_tick(&buttons[i]);
1227 }
1228 }; 60 };
1229 61
1230 return 0; 62 return 0;
diff --git a/src/sprites.h b/src/sprites.h
new file mode 100644
index 0000000..e7439f1
--- /dev/null
+++ b/src/sprites.h
@@ -0,0 +1,98 @@
1#ifndef GBAEXP_SPRITES_H
2#define GBAEXP_SPRITES_H
3
4#include "common.h"
5
6u32
7unpack_1bb(u8 hex) {
8 const u32 conversion_u32[16] = {
9 0x00000000, 0x00000001, 0x00000010, 0x00000011,
10 0x00000100, 0x00000101, 0x00000110, 0x00000111,
11 0x00001000, 0x00001001, 0x00001010, 0x00001011,
12 0x00001100, 0x00001101, 0x00001110, 0x00001111,
13 };
14 u8 low = hex & 0xF;
15 u8 high = (hex >> 4) & 0xF;
16 return (conversion_u32[high] << 16) | conversion_u32[low];
17}
18
19typedef struct Sprite {
20 // A unique sprite identifier.
21 size_t id;
22 // The number of tiles for a single sprite frame.
23 size_t n_tiles;
24 // The starting tile of this sprite.
25 size_t tile_start;
26 // The associated palette bank for this sprite.
27 size_t pal_bank;
28} Sprite;
29
30
31#define NUM_SPRITES 128
32Sprite sprites[NUM_SPRITES];
33
34// Keeping track of unique sprites and current sprite memory pointer using
35// global singletons.
36size_t sprite_counter = 0;
37size_t sprite_tile_counter = 0;
38u32 *sprite_memory = NULL;
39
40// Loads the sprite data into video memory and initialize the Sprite structure.
41size_t
42load_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) {
43 memcpy(sprite_memory, sprite_data, 8 * n_tiles * n_frames * sizeof(u32));
44 sprite_memory += 8 * n_tiles * n_frames;
45 Sprite sprite = {
46 .id = sprite_counter,
47 .n_tiles = n_tiles,
48 .tile_start = sprite_tile_counter,
49 };
50 sprite_tile_counter += n_tiles * n_frames;
51 sprites[sprite_counter] = sprite;
52 return sprite_counter++;
53}
54
55size_t
56load_packed_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) {
57 size_t counter = 0;
58 for (size_t i = 0; i < 8 * n_tiles * n_frames / 4; ++i) {
59 u32 hex = sprite_data[i];
60 sprite_memory[counter++] = unpack_1bb((hex >> 24) & 0xFF);
61 sprite_memory[counter++] = unpack_1bb((hex >> 16) & 0xFF);
62 sprite_memory[counter++] = unpack_1bb((hex >> 8) & 0xFF);
63 sprite_memory[counter++] = unpack_1bb((hex) & 0xFF);
64 }
65 sprite_memory += 8 * n_tiles * n_frames;
66 Sprite sprite = {
67 .id = sprite_counter,
68 .n_tiles = n_tiles,
69 .tile_start = sprite_tile_counter,
70 };
71 sprite_tile_counter += n_tiles * n_frames;
72 sprites[sprite_counter] = sprite;
73 return sprite_counter++;
74}
75
76void
77init_sprites(size_t starting_tile) {
78 // Initialize all attributes by disabling rendering. If we don't do this,
79 // glitches may appear.
80 for (size_t i = 0; i < 128; ++i) {
81 OBJ_ATTR_0(i) = (1 << 9);
82 }
83
84 sprite_counter = 0;
85 // Prepare global sprite_memory address.
86 sprite_memory = &TILE_MEM[4][starting_tile];
87}
88
89void
90init_sprite_pal(size_t starting_index, Color col) {
91 // Add colors to the sprite color palette. Tiles with color number 0 are
92 // treated as transparent.
93 for (size_t i = 0; i < 16; ++i) {
94 PAL_BUFFER_SPRITES[i + starting_index] = col;
95 }
96
97}
98#endif // GBAEXP_SPRITES_H