From 24887f28c3eb6d67a7c1a0803520b3fb728ee4f3 Mon Sep 17 00:00:00 2001 From: Bad Diode Date: Mon, 26 Apr 2021 11:11:36 +0200 Subject: Move code to relevant files for organization --- src/bitmap.h | 228 +++++++++++ src/common.h | 281 +++++++++++++ src/gba-buttons.c | 602 ++++++++++++++++++++++++++- src/main.c | 1184 +---------------------------------------------------- src/sprites.h | 98 +++++ 5 files changed, 1212 insertions(+), 1181 deletions(-) create mode 100644 src/bitmap.h create mode 100644 src/common.h create mode 100644 src/sprites.h 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 @@ +#ifndef GBAEXP_BITMAP_H +#define GBAEXP_BITMAP_H + +#include "bd-font.c" +#include "common.c" + +// Using bd-font, an 8x8 bitmap font. +static void +put_char(int x, int y, Color clr, u8 chr) { + for (size_t i = 0; i < 8; ++i) { + for (size_t j = 0; j < 8; ++j) { + if ((font[chr][i] >> (7 - j)) & 0x1) { + FRAMEBUFFER[y + i][x + j] = clr; + } + } + } +} + +static void +put_text(int x, int y, Color clr, char *msg) { + int count = 0; + while (*msg) { + put_char(x + count, y, clr, *msg++); + count += 8; + } +} + +// Draws a line with the given color between (x0,y0) and (x1,y1) using the +// Bresenham's line drawing algorithm using exclusively integer arithmetic. +static void +draw_line(int x0, int y0, int x1, int y1, Color clr) { + // Pointer to the initial position of the screen buffer where we will start + // writing our data. + vu16 *destination = (u16*)(SCREEN_BUFFER + y0 * SCREEN_WIDTH + x0); + + // Adjust the step direction and calculate deltas. + int x_step; + int y_step; + int dx; + int dy; + if (x0 > x1) { + x_step = -1; + dx = x0 - x1; + } else { + x_step = 1; + dx = x1 - x0; + } + if (y0 > y1) { + y_step = -SCREEN_WIDTH; + dy = y0 - y1; + } else { + y_step = +SCREEN_WIDTH; + dy = y1 - y0; + } + + if(dy == 0) { + // Horizontal line. + for(int i = 0; i <= dx; i++) { + destination[i * x_step] = clr; + } + } else if(dx == 0) { + // Vertical line. + for(int i = 0; i <= dy; i++) { + destination[i * y_step] = clr; + } + } else if (dx >= dy){ + // Positive slope. + int diff = 2 * dy - dx; + for (int i = 0; i <= dx; ++i) { + *destination = clr; + if (diff >= 0) { + destination += y_step; + diff -= 2 * dx; + } + destination += x_step; + diff += 2 * dy; + } + } else { + // Negative slope. + int diff = 2 * dx - dy; + for (int i = 0; i <= dy; ++i) { + *destination = clr; + if (diff >= 0) { + destination += x_step; + diff -= 2 * dy; + } + destination += y_step; + diff += 2 * dx; + } + } +} + +static inline void +draw_rect(int x0, int y0, int x1, int y1, Color clr) { + if (x0 > x1) { + int tmp = x0; + x0 = x1; + x1 = tmp; + } + if (y0 > y1) { + int tmp = y0; + y0 = y1; + y1 = tmp; + } + int dx = x1 - x0; + int dy = y1 - y0; + for (int i = 0; i <= dx; ++i) { + int x = x0 + i; + FRAMEBUFFER[y0][x] = clr; + FRAMEBUFFER[y1][x] = clr; + } + for (int j = 0; j <= dy; ++j) { + int y = y0 + j; + FRAMEBUFFER[y][x0] = clr; + FRAMEBUFFER[y][x1] = clr; + } +} + +static inline void +draw_fill_rect(int x0, int y0, int x1, int y1, Color clr) { + if (x0 > x1) { + int tmp = x0; + x0 = x1; + x1 = tmp; + } + if (y0 > y1) { + int tmp = y0; + y0 = y1; + y1 = tmp; + } + int dx = x1 - x0; + int dy = y1 - y0; + for (int i = 0; i <= dx; ++i) { + for (int j = 0; j <= dy; ++j) { + int x = x0 + i; + int y = y0 + j; + FRAMEBUFFER[y][x] = clr; + } + } +} + +// In Mode4 the buffer is of 8 bytes per pixel instead of 16. We can't write the +// color directly, instead the color is stored in the palette memory at +// `MEM_PAL`. Note that in this mode MEM_PAL[0] is the background color. This +// plotter takes an index to a color stored in MEM_PAL[col_index]. Because the +// GBA needs to meet memory alignment requirements, we can't write a u8 into +// memory, instead we need to read a u16 word, mask and or the corresponding +// bits and wave the updated u16. +static void +put_pixel_m4(int x, int y, u8 col_index, vu16 *buffer) { + int buffer_index = (y * SCREEN_WIDTH + x) / 2; + vu16 *destination = &buffer[buffer_index]; + // Odd pixels will go to the top 8 bits of the destination. Even pixels to + // the lower 8 bits. + int odd = x & 0x1; + if(odd) { + *destination= (*destination & 0xFF) | (col_index << 8); + } else { + *destination= (*destination & ~0xFF) | col_index; + } +} + +static void +draw_fill_rect_m4(int x0, int y0, int x1, int y1, u8 col_index, vu16 *buffer) { + int ix, iy; + for(iy = y0; iy < y1; iy++) { + for(ix = x0; ix < x1; ix++) { + put_pixel_m4(ix, iy, col_index, buffer); + } + } +} + +void +draw_logo(void) { + int side = 60; + int line = 35; + int height = side * 0.5; + int x = SCREEN_WIDTH / 2 - height / 2; + int y = SCREEN_HEIGHT / 2; + + // Draw red triangle. + draw_line(x + height - 1, y - side / 2, x, y - 1, COLOR_RED); + draw_line(x + height - 1, y + side / 2, x, y + 1, COLOR_RED); + draw_line(x + height - 1, y - side / 2 + 1, x, y, COLOR_RED); + draw_line(x + height - 1, y + side / 2 - 1, x, y, COLOR_RED); + + // Draw white triangle. + draw_line(x, y - side / 2, x, y + side / 2, COLOR_WHITE); + draw_line(x + 1, y - side / 2, x + height, y - 1, COLOR_WHITE); + draw_line(x + 1, y + side / 2, x + height, y + 1, COLOR_WHITE); + + // Draw white line at triangle tip. + draw_line(x + height, y - side / 2, x + height, y + side / 2, COLOR_WHITE); + draw_line(x + height + 1, y - side / 2, x + height + 1, y + side / 2, COLOR_WHITE); + + // Double triangle line. + draw_line(x - 1, y - side / 2, x - 1, y + side / 2, COLOR_WHITE); + draw_line(x + 1, y - side / 2 + 1, x + height, y, COLOR_WHITE); + draw_line(x + 1, y + side / 2 - 1, x + height, y, COLOR_WHITE); + + // Draw white lines. + draw_line(x - line, y, x, y, COLOR_WHITE); + draw_line(x + height, y, x + height + line, y, COLOR_WHITE); + draw_line(x - line, y + 1, x, y + 1, COLOR_WHITE); + draw_line(x + height, y + 1, x + height + line, y + 1, COLOR_WHITE); +} + +void +copy_font_to_tile_memory(Tile *tile) { + // Hex to bits translation table. + const u32 conversion_u32[16] = { + 0x00000000, 0x00001000, 0x00000100, 0x00001100, + 0x00000010, 0x00001010, 0x00000110, 0x00001110, + 0x00000001, 0x00001001, 0x00000101, 0x00001101, + 0x00000011, 0x00001011, 0x00000111, 0x00001111, + }; + for (size_t i = 0; i < 250; ++i) { + for (size_t j = 0; j < 8; ++j) { + u8 row = font[i][j]; + u32 tile_idx = 0x00000000; + tile_idx = conversion_u32[row & 0xF] << 16; + tile_idx |= conversion_u32[(row >> 4) & 0xF]; + (tile + i)->data[j] = tile_idx; + } + } +} + +#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 @@ +#ifndef GBAEXP_COMMON_H +#define GBAEXP_COMMON_H + +#include "shorthand.h" + +// +// Memory sections. +// + +// Defines for the different memory sections in the GBA. +#define MEM_SROM 0x00000000 +#define MEM_EW 0x02000000 +#define MEM_IW 0x03000000 +#define MEM_IO 0x04000000 +#define MEM_PAL 0x05000000 +#define MEM_VRAM 0x06000000 +#define MEM_OAM 0x07000000 +#define MEM_PAK 0x08000000 +#define MEM_CART 0x0E000000 + +// +// Display modes. +// + +// Display registers. +#define DISP_CTRL *((vu32*)(MEM_IO + 0x0000)) +#define DISP_STATUS *((vu16*)(MEM_IO + 0x0004)) +#define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006)) + +// The first three bits in the DISP_CTRL are used to control the video mode. +#define DISP_MODE_0 0x0000 +#define DISP_MODE_1 0x0001 +#define DISP_MODE_2 0x0002 +#define DISP_MODE_3 0x0003 +#define DISP_MODE_4 0x0004 +#define DISP_MODE_5 0x0005 +#define DISP_GB (1 << 3) +#define DISP_PAGE (1 << 4) +#define DISP_OAM_HBLANK (1 << 5) +#define DISP_OBJ_1D (1 << 6) +#define DISP_BLANK (1 << 7) +#define DISP_BG_0 (1 << 8) +#define DISP_BG_1 (1 << 9) +#define DISP_BG_2 (1 << 10) +#define DISP_BG_3 (1 << 11) +#define DISP_OBJ (1 << 12) +#define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D + +// Registers to control of BG layers. +#define BG_CTRL_0 *((vu16*)(0x04000008 + 0x0002 * 0)) +#define BG_CTRL_1 *((vu16*)(0x04000008 + 0x0002 * 1)) +#define BG_CTRL_2 *((vu16*)(0x04000008 + 0x0002 * 2)) +#define BG_CTRL_3 *((vu16*)(0x04000008 + 0x0002 * 3)) + +// Bits to control the background. +#define BG_PRIORITY_0 0x0 +#define BG_PRIORITY_1 0x1 +#define BG_PRIORITY_2 0x2 +#define BG_PRIORITY_3 0x3 +#define BG_CHARBLOCK(N) ((N) << 2) +#define BG_MOSAIC (1 << 6) +#define BG_HIGH_COLOR (1 << 7) +#define BG_SCREENBLOCK(N) ((N) << 8) +#define BG_AFFINE (1 << 0xD) +#define BG_SIZE(N) ((N) << 0xE) + +// BG registers for horizontal displacement. +#define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0)) +#define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1)) +#define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2)) +#define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3)) + +// BG registers for vertical displacement. +#define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0)) +#define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1)) +#define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2)) +#define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3)) + +// Screen settings. +#define SCREEN_WIDTH 240 +#define SCREEN_HEIGHT 160 + +// The GBA in mode 3 expects rbg15 colors in the VRAM, where each component +// (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). +typedef u16 Color; + +// +// Tile memory access. +// + +// NOTE: Only defining 4bpp tiles for now. +typedef struct Tile { + u32 data[8]; +} Tile; + +typedef Tile TileBlock[512]; +#define TILE_MEM ((TileBlock*) MEM_VRAM) + +typedef u16 ScreenBlock[1024]; +#define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) + +// We can treat the screen as a HxW matrix. With the following macro we can +// write a pixel to the screen at the (x, y) position using: +// +// FRAMEBUFFER[y][x] = color; +// +typedef Color Scanline[SCREEN_WIDTH]; +#define FRAMEBUFFER ((Scanline*)MEM_VRAM) +#define SCREEN_BUFFER ((u16*) MEM_VRAM) +#define PAL_BUFFER_BG ((u16*) MEM_PAL) +#define PAL_BUFFER_SPRITES ((u16*) 0x05000200) + +// +// Colors. +// + +static inline Color +rgb15(u32 red, u32 green, u32 blue ) { + return (blue << 10) | (green << 5) | red; +} + +#define COLOR_RED rgb15(31, 0, 12) +#define COLOR_BLUE rgb15(2, 15, 30) +#define COLOR_CYAN rgb15(0, 30, 30) +#define COLOR_GREY rgb15(4, 4, 4) +#define COLOR_BLACK rgb15(0, 0, 0) +#define COLOR_WHITE rgb15(28, 28, 28) + +// +// Sprites. +// + +// Using macros instead of aligned structs for setting up OBJ attributes and +// affine parameters. +// TODO: Benchmark if this would be slower or the same that TONC's +// implementation. +#define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N))) +#define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N))) +#define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N))) +#define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N))) +#define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N))) +#define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N))) +#define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N))) + +// OBJ_ATTR_0 parameters +#define OBJ_Y_COORD(N) ((N) & 0xFF) +#define OBJ_NORMAL (0x00 << 0x8) +#define OBJ_AFFINE (0x01 << 0x8) +#define OBJ_HIDDEN (0x02 << 0x8) +#define OBJ_AFFINE_2X (0x03 << 0x8) +#define OBJ_ALPHA_BLEND (0x01 << 0xA) +#define OBJ_WINDOW (0x02 << 0xA) +#define OBJ_SHAPE_SQUARE (0x00 << 0xE) +#define OBJ_SHAPE_WIDE (0x01 << 0xE) +#define OBJ_SHAPE_TALL (0x02 << 0xE) + +// OBJ_ATTR_1 parameters +#define OBJ_X_COORD(N) ((N) & 0x1FF) +#define OBJ_AFFINE_IDX(N) ((N) << 0x9) +#define OBJ_H_FLIP (0x01 << 0xC) +#define OBJ_V_FLIP (0x01 << 0xD) +#define OBJ_SIZE_SMALL (0x00 << 0xE) +#define OBJ_SIZE_MID (0x01 << 0xE) +#define OBJ_SIZE_BIG (0x02 << 0xE) +#define OBJ_SIZE_HUGE (0x03 << 0xE) + +// OBJ_ATTR_2 parameters +#define OBJ_TILE_INDEX(N) ((N) & 0x3FF) +#define OBJ_PRIORITY(N) ((N) << 0xA) +#define OBJ_PAL_BANK(N) ((N) << 0xC) + +static inline void +wait_vsync(void) { + while(DISP_VCOUNT >= 160); + while(DISP_VCOUNT < 160); +} + +// +// Mode 4 page flipping +// + +static inline void +flip_page(void) { + DISP_CTRL ^= DISP_PAGE; +} + +#define SCREEN_PAGE_1 ((vu16*) MEM_VRAM) +#define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000)) + +// +// Profiling. +// + +#define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0)) +#define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1)) +#define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2)) +#define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3)) +#define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0)) +#define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1)) +#define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2)) +#define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3)) + +// Timer control bits. +#define TIMER_CTRL_FREQ_0 0 +#define TIMER_CTRL_FREQ_1 1 +#define TIMER_CTRL_FREQ_2 2 +#define TIMER_CTRL_FREQ_3 3 +#define TIMER_CTRL_CASCADE (1 << 2) +#define TIMER_CTRL_IRQ (1 << 6) +#define TIMER_CTRL_ENABLE (1 << 7) + +// We use timers 2 and 3 to count the number of cycles since the profile_start +// functions is called. Don't use if the code we are trying to profile make use +// of these timers. +static inline +void profile_start(void) { + TIMER_DATA_2 = 0; + TIMER_DATA_3 = 0; + TIMER_CTRL_2 = 0; + TIMER_CTRL_3 = 0; + TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; + TIMER_CTRL_2 = TIMER_CTRL_ENABLE; +} + +static inline +u32 profile_stop(void) { + TIMER_CTRL_2 = 0; + return (TIMER_DATA_3 << 16) | TIMER_DATA_2; +} + +// +// Input handling. +// + +// Memory address for key input register +#define KEY_INPUTS *((vu16*) 0x04000130) + +// Alias for key pressing bits. +#define KEY_A (1 << 0) +#define KEY_B (1 << 1) +#define KEY_SELECT (1 << 2) +#define KEY_START (1 << 3) +#define KEY_RIGHT (1 << 4) +#define KEY_LEFT (1 << 5) +#define KEY_UP (1 << 6) +#define KEY_DOWN (1 << 7) +#define KEY_R (1 << 8) +#define KEY_L (1 << 9) + +#define KEY_MASK 0x03FF + +// Saving the previous and current key states as globals for now. +static u16 key_curr = 0; +static u16 key_prev = 0; + +static inline void +poll_keys(void) { + key_prev = key_curr; + key_curr = ~KEY_INPUTS & KEY_MASK; +} + +// Returns true if the given key has been pressed at time of calling and was not +// pressed since the previous call. For example, if a key is being held, this +// function will return `true` only on the frame where the key initially +// activated. +static inline u32 +key_pressed(u32 key) { + return (key_curr & key) & ~(key_prev & key); +} + +// Check if the given key is pressed and has been since at least one frame. +static inline u32 +key_hold(u32 key) { + return (key_curr & key) & key_prev & key; +} + +// Check if the given key/button is currently pressed. +#define KEY_PRESSED(key) (~(KEY_INPUTS) & key) + + +#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 @@ +#include "common.h" +#include "sprites.h" + // 1bpp packed u32 gba_btn_a_data[] = { 0xc0300804, 0x844242c2, 0x030c1020, 0x21424243, @@ -133,11 +136,6 @@ u32 gba_btn_fx_startselect[] = { 0x01800000, 0x00000000, 0x00000000, 0x00000004, }; -typedef struct SpriteAnimation { - size_t *tile_offsets; - size_t n_frames; -} SpriteAnimation; - typedef enum {BTN_STATE_IDLE, BTN_STATE_PRESSED, BTN_STATE_RELEASED, BTN_STATE_HOLD} BtnState; typedef struct AnimationEntry { @@ -262,3 +260,597 @@ AnimationEntry *btn_animation_startselect[] = { {0, 0, 12, 1}, }, }; + +typedef struct ButtonSprite { + ObjState *sprites; + AnimationEntry **animations; + size_t frame; + size_t n_obj; + size_t n_frames; + BtnState state; +} ButtonSprite; + +void +init_button_sprite(ButtonSprite *btn) { + for (size_t i = 0; i < btn->n_obj; ++i) { + btn->sprites[i].id = load_packed_sprite_data( + btn->sprites[i].data, + btn->sprites[i].n_tiles, + btn->sprites[i].frames); + btn->sprites[i].base_tile = sprites[btn->sprites[i].id].tile_start; + } +} + +void +button_tick(ButtonSprite *btn) { + // Nothing to do here. + if (btn->state == BTN_STATE_IDLE) { + return; + } + + // Reset animation state. + if (btn->state == BTN_STATE_PRESSED && btn->frame != 0) { + btn->frame = 0; + } + + // Continue the animation. + if (btn->state == BTN_STATE_HOLD || btn->state == BTN_STATE_PRESSED ) { + if(btn->frame < btn->n_frames - 1) { + btn->frame++; + } + } + + // Finish the animation and return to idle. + if (btn->state == BTN_STATE_RELEASED) { + if (btn->frame > 0 && btn->frame < btn->n_frames - 1) { + btn->frame++; + } else { + btn->frame = 0; + btn->state = BTN_STATE_IDLE; + } + } + for (size_t i = 0; i < btn->n_obj; ++i) { + AnimationEntry anim_frame = btn->animations[i][btn->frame]; + int x = btn->sprites[i].x + anim_frame.x_offset; + int y = btn->sprites[i].y + anim_frame.y_offset; + int base_tile = btn->sprites[i].base_tile + anim_frame.tile_offset; + + // Clear the previous x/y coordinate and base tiles. + btn->sprites[i].obj_attr_0 &= ~0xFF; + btn->sprites[i].obj_attr_1 &= ~0x1FF; + btn->sprites[i].obj_attr_2 &= ~0x3FF; + + // Update x/y/tile and hidden state from the animations. + btn->sprites[i].obj_attr_0 |= OBJ_Y_COORD(y); + btn->sprites[i].obj_attr_1 |= OBJ_X_COORD(x); + btn->sprites[i].obj_attr_2 |= base_tile; + if (anim_frame.hidden) { + btn->sprites[i].obj_attr_0 |= OBJ_HIDDEN; + } else { + btn->sprites[i].obj_attr_0 &= ~OBJ_HIDDEN; + } + + // Update OBJ attributes. + OBJ_ATTR_0(btn->sprites[i].id) = btn->sprites[i].obj_attr_0; + OBJ_ATTR_1(btn->sprites[i].id) = btn->sprites[i].obj_attr_1; + OBJ_ATTR_2(btn->sprites[i].id) = btn->sprites[i].obj_attr_2; + } +} + +ButtonSprite buttons[] = { + // DOWN. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16, + .y = SCREEN_HEIGHT / 2 + 29, + .data = &gba_btn_updown_data, + .n_tiles = 4, + .frames = 1, + .obj_attr_0 = 0, + .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16, + .y = SCREEN_HEIGHT / 2 + 29 + 11, + .data = &gba_btn_down_shadow_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16 - 8, + .y = SCREEN_HEIGHT / 2 + 29 + 17, + .data = &gba_btn_fx_downup, + .n_tiles = 4, + .frames = 4, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + }, + }, + // UP. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16, + .y = SCREEN_HEIGHT / 2 + 32 - 18, + .data = &gba_btn_updown_data, + .n_tiles = 4, + .frames = 1, + .obj_attr_0 = 0, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16, + .y = SCREEN_HEIGHT / 2 + 32 - 18 + 7, + .data = &gba_btn_up_shadow_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16 - 8, + .y = SCREEN_HEIGHT / 2 + 32 - 18 - 7, + .data = &gba_btn_fx_downup, + .n_tiles = 4, + .frames = 4, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + }, + }, + // LEFT + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16 - 10, + .y = SCREEN_HEIGHT / 2 + 32 - 10, + .data = &gba_btn_leftright_data, + .n_tiles = 4, + .frames = 1, + .obj_attr_0 = 0, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16 - 10, + .y = SCREEN_HEIGHT / 2 + 32 - 10 + 6, + .data = &gba_btn_leftright_shadow_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16 - 10 - 6, + .y = SCREEN_HEIGHT / 2 + 32 - 10 - 8, + .data = &gba_btn_fx_leftright, + .n_tiles = 4, + .frames = 4, + .obj_attr_0 = OBJ_SHAPE_TALL, + .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + }, + }, + // RIGHT. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16 + 11, + .y = SCREEN_HEIGHT / 2 + 32 - 10, + .data = &gba_btn_leftright_data, + .n_tiles = 4, + .frames = 1, + .obj_attr_0 = 0, + .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16 + 11, + .y = SCREEN_HEIGHT / 2 + 32 - 10 + 6, + .data = &gba_btn_leftright_shadow_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 16 + 11 + 14, + .y = SCREEN_HEIGHT / 2 + 32 - 10 - 8, + .data = &gba_btn_fx_leftright, + .n_tiles = 4, + .frames = 4, + .obj_attr_0 = OBJ_SHAPE_TALL, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + }, + }, + // L. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 28, + .y = SCREEN_HEIGHT / 2 - 32 - 20, + .data = &gba_btn_l_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 28, + .y = SCREEN_HEIGHT / 2 - 32 - 20 + 2, + .data = &gba_btn_lr_shadow_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 64 - 28 - 12, + .y = SCREEN_HEIGHT / 2 - 32 - 20 - 12, + .data = &gba_btn_fx_lr, + .n_tiles = 4, + .frames = 4, + .obj_attr_0 = OBJ_SHAPE_SQUARE, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + }, + }, + // R. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 + 20 + 24, + .y = SCREEN_HEIGHT / 2 - 32 - 20, + .data = &gba_btn_r_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 + 20 + 24, + .y = SCREEN_HEIGHT / 2 - 32 - 20 + 2, + .data = &gba_btn_lr_shadow_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 + 20 + 12 + 24, + .y = SCREEN_HEIGHT / 2 - 32 - 20 - 12, + .data = &gba_btn_fx_lr, + .n_tiles = 4, + .frames = 4, + .obj_attr_0 = OBJ_SHAPE_SQUARE, + .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + }, + }, + // A. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation_ab, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 + 20 + 20, + .y = SCREEN_HEIGHT / 2 + 32 - 16, + .data = &gba_btn_a_data, + .n_tiles = 4, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_SQUARE, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 + 20 + 20, + .y = SCREEN_HEIGHT / 2 + 32 - 16 + 8, + .data = &gba_btn_ab_shadow_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 + 19 - 7 + 20, + .y = SCREEN_HEIGHT / 2 + 32 - 23, + .data = &gba_btn_fx_ab, + .n_tiles = 16, + .frames = 3, + .obj_attr_0 = OBJ_SHAPE_SQUARE, + .obj_attr_1 = OBJ_SIZE_BIG, + .obj_attr_2 = 0 + }, + }, + }, + // B. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation_ab, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 + 20, + .y = SCREEN_HEIGHT / 2 + 32, + .data = &gba_btn_b_data, + .n_tiles = 4, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_SQUARE, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 + 20, + .y = SCREEN_HEIGHT / 2 + 32 + 8, + .data = &gba_btn_ab_shadow_data, + .n_tiles = 2, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 + 32 - 8 + 20, + .y = SCREEN_HEIGHT / 2 + 33 + 8 - 16, + .data = &gba_btn_fx_ab, + .n_tiles = 16, + .frames = 3, + .obj_attr_0 = OBJ_SHAPE_SQUARE, + .obj_attr_1 = OBJ_SIZE_BIG, + .obj_attr_2 = 0 + }, + }, + }, + // START. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation_startselect, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 10 + 26 - 14, + .y = SCREEN_HEIGHT / 2 + 40 + 10, + .data = &gba_btn_startselect_data, + .n_tiles = 2, + .frames = 2, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 18 + 26 - 14, + .y = SCREEN_HEIGHT / 2 + 46 + 10, + .data = &gba_btn_start_text, + .n_tiles = 4, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 19 + 26 - 14, + .y = SCREEN_HEIGHT / 2 + 37 + 10, + .data = &gba_btn_fx_startselect, + .n_tiles = 4, + .frames = 4, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + }, + }, + // SELECT. + { + .frame = 0, + .n_obj = 3, + .n_frames = 8, + .state = BTN_STATE_RELEASED, + .animations = &btn_animation_startselect, + .sprites = &(ObjState[]){ + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 10 - 14, + .y = SCREEN_HEIGHT / 2 + 40 + 10, + .data = &gba_btn_startselect_data, + .n_tiles = 2, + .frames = 2, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_SMALL, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 18 - 14, + .y = SCREEN_HEIGHT / 2 + 46 + 10, + .data = &gba_btn_select_text, + .n_tiles = 4, + .frames = 1, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + { + .id = 0, + .x = SCREEN_WIDTH / 2 - 19 - 14, + .y = SCREEN_HEIGHT / 2 + 37 + 10, + .data = &gba_btn_fx_startselect, + .n_tiles = 4, + .frames = 4, + .obj_attr_0 = OBJ_SHAPE_WIDE, + .obj_attr_1 = OBJ_SIZE_MID, + .obj_attr_2 = 0 + }, + }, + }, +}; + +static inline void +init_button_sprites(void) { + for (size_t i = 0; i < sizeof(buttons) / sizeof(ButtonSprite); ++i) { + init_button_sprite(&buttons[i]); + } +} + +static inline void +update_button_sprites(void) { + if (key_pressed(KEY_DOWN)) { + buttons[0].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_DOWN)) { + buttons[0].state = BTN_STATE_HOLD; + } else { + buttons[0].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_UP)) { + buttons[1].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_UP)) { + buttons[1].state = BTN_STATE_HOLD; + } else { + buttons[1].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_LEFT)) { + buttons[2].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_LEFT)) { + buttons[2].state = BTN_STATE_HOLD; + } else { + buttons[2].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_RIGHT)) { + buttons[3].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_RIGHT)) { + buttons[3].state = BTN_STATE_HOLD; + } else { + buttons[3].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_L)) { + buttons[4].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_L)) { + buttons[4].state = BTN_STATE_HOLD; + } else { + buttons[4].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_R)) { + buttons[5].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_R)) { + buttons[5].state = BTN_STATE_HOLD; + } else { + buttons[5].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_A)) { + buttons[6].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_A)) { + buttons[6].state = BTN_STATE_HOLD; + } else { + buttons[6].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_B)) { + buttons[7].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_B)) { + buttons[7].state = BTN_STATE_HOLD; + } else { + buttons[7].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_START)) { + buttons[8].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_START)) { + buttons[8].state = BTN_STATE_HOLD; + } else { + buttons[8].state = BTN_STATE_RELEASED; + } + if (key_pressed(KEY_SELECT)) { + buttons[9].state = BTN_STATE_PRESSED; + } else if (key_hold(KEY_SELECT)) { + buttons[9].state = BTN_STATE_HOLD; + } else { + buttons[9].state = BTN_STATE_RELEASED; + } + + for (size_t i = 0; i < sizeof(buttons) / sizeof(ButtonSprite); ++i) { + button_tick(&buttons[i]); + } +} 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 @@ #include -#include "shorthand.h" -#include "bd-font.c" +#include "common.h" #include "gba-buttons.c" #include "background-tiles.c" - -// -// Memory sections. -// - -// Defines for the different memory sections in the GBA. -#define MEM_SROM 0x00000000 -#define MEM_EW 0x02000000 -#define MEM_IW 0x03000000 -#define MEM_IO 0x04000000 -#define MEM_PAL 0x05000000 -#define MEM_VRAM 0x06000000 -#define MEM_OAM 0x07000000 -#define MEM_PAK 0x08000000 -#define MEM_CART 0x0E000000 - -// -// Display modes. -// - -// Display registers. -#define DISP_CTRL *((vu32*)(MEM_IO + 0x0000)) -#define DISP_STATUS *((vu16*)(MEM_IO + 0x0004)) -#define DISP_VCOUNT *((vu16*)(MEM_IO + 0x0006)) - -// The first three bits in the DISP_CTRL are used to control the video mode. -#define DISP_MODE_0 0x0000 -#define DISP_MODE_1 0x0001 -#define DISP_MODE_2 0x0002 -#define DISP_MODE_3 0x0003 -#define DISP_MODE_4 0x0004 -#define DISP_MODE_5 0x0005 -#define DISP_GB (1 << 3) -#define DISP_PAGE (1 << 4) -#define DISP_OAM_HBLANK (1 << 5) -#define DISP_OBJ_1D (1 << 6) -#define DISP_BLANK (1 << 7) -#define DISP_BG_0 (1 << 8) -#define DISP_BG_1 (1 << 9) -#define DISP_BG_2 (1 << 10) -#define DISP_BG_3 (1 << 11) -#define DISP_OBJ (1 << 12) -#define DISP_ENABLE_SPRITES DISP_OBJ | DISP_OBJ_1D - -// Registers to control of BG layers. -#define BG_CTRL_0 *((vu16*)(0x04000008 + 0x0002 * 0)) -#define BG_CTRL_1 *((vu16*)(0x04000008 + 0x0002 * 1)) -#define BG_CTRL_2 *((vu16*)(0x04000008 + 0x0002 * 2)) -#define BG_CTRL_3 *((vu16*)(0x04000008 + 0x0002 * 3)) - -// Bits to control the background. -#define BG_PRIORITY_0 0x0 -#define BG_PRIORITY_1 0x1 -#define BG_PRIORITY_2 0x2 -#define BG_PRIORITY_3 0x3 -#define BG_CHARBLOCK(N) ((N) << 2) -#define BG_MOSAIC (1 << 6) -#define BG_HIGH_COLOR (1 << 7) -#define BG_SCREENBLOCK(N) ((N) << 8) -#define BG_AFFINE (1 << 0xD) -#define BG_SIZE(N) ((N) << 0xE) - -// BG registers for horizontal displacement. -#define BG_H_SCROLL_0 *((vu16*)(0x04000010 + 0x0004 * 0)) -#define BG_H_SCROLL_1 *((vu16*)(0x04000010 + 0x0004 * 1)) -#define BG_H_SCROLL_2 *((vu16*)(0x04000010 + 0x0004 * 2)) -#define BG_H_SCROLL_3 *((vu16*)(0x04000010 + 0x0004 * 3)) - -// BG registers for vertical displacement. -#define BG_V_SCROLL_0 *((vu16*)(0x04000012 + 0x0004 * 0)) -#define BG_V_SCROLL_1 *((vu16*)(0x04000012 + 0x0004 * 1)) -#define BG_V_SCROLL_2 *((vu16*)(0x04000012 + 0x0004 * 2)) -#define BG_V_SCROLL_3 *((vu16*)(0x04000012 + 0x0004 * 3)) - -// Screen settings. -#define SCREEN_WIDTH 240 -#define SCREEN_HEIGHT 160 - -// The GBA in mode 3 expects rbg15 colors in the VRAM, where each component -// (RGB) have a 0--31 range. For example, pure red would be rgb15(31, 0, 0). -typedef u16 Color; - -// -// Tile memory access. -// - -// NOTE: Only defining 4bpp tiles for now. -typedef struct Tile { - u32 data[8]; -} Tile; - -typedef Tile TileBlock[512]; -#define TILE_MEM ((TileBlock*) MEM_VRAM) - -typedef u16 ScreenBlock[1024]; -#define SCREENBLOCK_MEM ((ScreenBlock*)MEM_VRAM) - -// We can treat the screen as a HxW matrix. With the following macro we can -// write a pixel to the screen at the (x, y) position using: -// -// FRAMEBUFFER[y][x] = color; -// -typedef Color Scanline[SCREEN_WIDTH]; -#define FRAMEBUFFER ((Scanline*)MEM_VRAM) -#define SCREEN_BUFFER ((u16*) MEM_VRAM) -#define PAL_BUFFER_BG ((u16*) MEM_PAL) -#define PAL_BUFFER_SPRITES ((u16*) 0x05000200) - -// -// Colors. -// - -static inline Color -rgb15(u32 red, u32 green, u32 blue ) { - return (blue << 10) | (green << 5) | red; -} - -#define COLOR_RED rgb15(31, 0, 12) -#define COLOR_BLUE rgb15(2, 15, 30) -#define COLOR_CYAN rgb15(0, 30, 30) -#define COLOR_GREY rgb15(4, 4, 4) -#define COLOR_BLACK rgb15(0, 0, 0) -#define COLOR_WHITE rgb15(28, 28, 28) - -// -// Sprites. -// - -// Using macros instead of aligned structs for setting up OBJ attributes and -// affine parameters. -// TODO: Benchmark if this would be slower or the same that TONC's -// implementation. -#define OBJ_ATTR_0(N) *((vu16*)(MEM_OAM + 0 + 8 * (N))) -#define OBJ_ATTR_1(N) *((vu16*)(MEM_OAM + 2 + 8 * (N))) -#define OBJ_ATTR_2(N) *((vu16*)(MEM_OAM + 4 + 8 * (N))) -#define OBJ_AFFINE_PA(N) *((vs16*)(MEM_OAM + 6 + 8 * 0 + 8 * 4 * (N))) -#define OBJ_AFFINE_PB(N) *((vs16*)(MEM_OAM + 6 + 8 * 1 + 8 * 4 * (N))) -#define OBJ_AFFINE_PC(N) *((vs16*)(MEM_OAM + 6 + 8 * 2 + 8 * 4 * (N))) -#define OBJ_AFFINE_PD(N) *((vs16*)(MEM_OAM + 6 + 8 * 3 + 8 * 4 * (N))) - -// OBJ_ATTR_0 parameters -#define OBJ_Y_COORD(N) ((N) & 0xFF) -#define OBJ_NORMAL (0x00 << 0x8) -#define OBJ_AFFINE (0x01 << 0x8) -#define OBJ_HIDDEN (0x02 << 0x8) -#define OBJ_AFFINE_2X (0x03 << 0x8) -#define OBJ_ALPHA_BLEND (0x01 << 0xA) -#define OBJ_WINDOW (0x02 << 0xA) -#define OBJ_SHAPE_SQUARE (0x00 << 0xE) -#define OBJ_SHAPE_WIDE (0x01 << 0xE) -#define OBJ_SHAPE_TALL (0x02 << 0xE) - -// OBJ_ATTR_1 parameters -#define OBJ_X_COORD(N) ((N) & 0x1FF) -#define OBJ_AFFINE_IDX(N) ((N) << 0x9) -#define OBJ_H_FLIP (0x01 << 0xC) -#define OBJ_V_FLIP (0x01 << 0xD) -#define OBJ_SIZE_SMALL (0x00 << 0xE) -#define OBJ_SIZE_MID (0x01 << 0xE) -#define OBJ_SIZE_BIG (0x02 << 0xE) -#define OBJ_SIZE_HUGE (0x03 << 0xE) - -// OBJ_ATTR_2 parameters -#define OBJ_TILE_INDEX(N) ((N) & 0x3FF) -#define OBJ_PRIORITY(N) ((N) << 0xA) -#define OBJ_PAL_BANK(N) ((N) << 0xC) - -// Using bd-font, an 8x8 bitmap font. -static void -put_char(int x, int y, Color clr, u8 chr) { - for (size_t i = 0; i < 8; ++i) { - for (size_t j = 0; j < 8; ++j) { - if ((font[chr][i] >> (7 - j)) & 0x1) { - FRAMEBUFFER[y + i][x + j] = clr; - } - } - } -} - -static void -put_text(int x, int y, Color clr, char *msg) { - int count = 0; - while (*msg) { - put_char(x + count, y, clr, *msg++); - count += 8; - } -} - -// Draws a line with the given color between (x0,y0) and (x1,y1) using the -// Bresenham's line drawing algorithm using exclusively integer arithmetic. -static void -draw_line(int x0, int y0, int x1, int y1, Color clr) { - // Pointer to the initial position of the screen buffer where we will start - // writing our data. - vu16 *destination = (u16*)(SCREEN_BUFFER + y0 * SCREEN_WIDTH + x0); - - // Adjust the step direction and calculate deltas. - int x_step; - int y_step; - int dx; - int dy; - if (x0 > x1) { - x_step = -1; - dx = x0 - x1; - } else { - x_step = 1; - dx = x1 - x0; - } - if (y0 > y1) { - y_step = -SCREEN_WIDTH; - dy = y0 - y1; - } else { - y_step = +SCREEN_WIDTH; - dy = y1 - y0; - } - - if(dy == 0) { - // Horizontal line. - for(int i = 0; i <= dx; i++) { - destination[i * x_step] = clr; - } - } else if(dx == 0) { - // Vertical line. - for(int i = 0; i <= dy; i++) { - destination[i * y_step] = clr; - } - } else if (dx >= dy){ - // Positive slope. - int diff = 2 * dy - dx; - for (int i = 0; i <= dx; ++i) { - *destination = clr; - if (diff >= 0) { - destination += y_step; - diff -= 2 * dx; - } - destination += x_step; - diff += 2 * dy; - } - } else { - // Negative slope. - int diff = 2 * dx - dy; - for (int i = 0; i <= dy; ++i) { - *destination = clr; - if (diff >= 0) { - destination += x_step; - diff -= 2 * dy; - } - destination += y_step; - diff += 2 * dx; - } - } -} - -static inline void -draw_rect(int x0, int y0, int x1, int y1, Color clr) { - if (x0 > x1) { - int tmp = x0; - x0 = x1; - x1 = tmp; - } - if (y0 > y1) { - int tmp = y0; - y0 = y1; - y1 = tmp; - } - int dx = x1 - x0; - int dy = y1 - y0; - for (int i = 0; i <= dx; ++i) { - int x = x0 + i; - FRAMEBUFFER[y0][x] = clr; - FRAMEBUFFER[y1][x] = clr; - } - for (int j = 0; j <= dy; ++j) { - int y = y0 + j; - FRAMEBUFFER[y][x0] = clr; - FRAMEBUFFER[y][x1] = clr; - } -} - -static inline void -draw_fill_rect(int x0, int y0, int x1, int y1, Color clr) { - if (x0 > x1) { - int tmp = x0; - x0 = x1; - x1 = tmp; - } - if (y0 > y1) { - int tmp = y0; - y0 = y1; - y1 = tmp; - } - int dx = x1 - x0; - int dy = y1 - y0; - for (int i = 0; i <= dx; ++i) { - for (int j = 0; j <= dy; ++j) { - int x = x0 + i; - int y = y0 + j; - FRAMEBUFFER[y][x] = clr; - } - } -} - -static inline void -wait_vsync(void) { - while(DISP_VCOUNT >= 160); - while(DISP_VCOUNT < 160); -} +#include "sprites.h" // // Main functions. // -// In Mode4 the buffer is of 8 bytes per pixel instead of 16. We can't write the -// color directly, instead the color is stored in the palette memory at -// `MEM_PAL`. Note that in this mode MEM_PAL[0] is the background color. This -// plotter takes an index to a color stored in MEM_PAL[col_index]. Because the -// GBA needs to meet memory alignment requirements, we can't write a u8 into -// memory, instead we need to read a u16 word, mask and or the corresponding -// bits and wave the updated u16. -static void -put_pixel_m4(int x, int y, u8 col_index, vu16 *buffer) { - int buffer_index = (y * SCREEN_WIDTH + x) / 2; - vu16 *destination = &buffer[buffer_index]; - // Odd pixels will go to the top 8 bits of the destination. Even pixels to - // the lower 8 bits. - int odd = x & 0x1; - if(odd) { - *destination= (*destination & 0xFF) | (col_index << 8); - } else { - *destination= (*destination & ~0xFF) | col_index; - } -} - -static void -draw_fill_rect_m4(int x0, int y0, int x1, int y1, u8 col_index, vu16 *buffer) { - int ix, iy; - for(iy = y0; iy < y1; iy++) { - for(ix = x0; ix < x1; ix++) { - put_pixel_m4(ix, iy, col_index, buffer); - } - } -} - -static inline void -flip_page(void) { - DISP_CTRL ^= DISP_PAGE; -} - -#define SCREEN_PAGE_1 ((vu16*) MEM_VRAM) -#define SCREEN_PAGE_2 ((vu16*) (MEM_VRAM + 0xa000)) - -// -// Profiling. -// - -#define TIMER_DATA_0 *((vu16*) (0x04000100 + 0x04 * 0)) -#define TIMER_DATA_1 *((vu16*) (0x04000100 + 0x04 * 1)) -#define TIMER_DATA_2 *((vu16*) (0x04000100 + 0x04 * 2)) -#define TIMER_DATA_3 *((vu16*) (0x04000100 + 0x04 * 3)) -#define TIMER_CTRL_0 *((vu16*) (0x04000102 + 0x04 * 0)) -#define TIMER_CTRL_1 *((vu16*) (0x04000102 + 0x04 * 1)) -#define TIMER_CTRL_2 *((vu16*) (0x04000102 + 0x04 * 2)) -#define TIMER_CTRL_3 *((vu16*) (0x04000102 + 0x04 * 3)) - -// Timer control bits. -#define TIMER_CTRL_FREQ_0 0 -#define TIMER_CTRL_FREQ_1 1 -#define TIMER_CTRL_FREQ_2 2 -#define TIMER_CTRL_FREQ_3 3 -#define TIMER_CTRL_CASCADE (1 << 2) -#define TIMER_CTRL_IRQ (1 << 6) -#define TIMER_CTRL_ENABLE (1 << 7) - -// We use timers 2 and 3 to count the number of cycles since the profile_start -// functions is called. Don't use if the code we are trying to profile make use -// of these timers. -static inline -void profile_start(void) { - TIMER_DATA_2 = 0; - TIMER_DATA_3 = 0; - TIMER_CTRL_2 = 0; - TIMER_CTRL_3 = 0; - TIMER_CTRL_3 = TIMER_CTRL_ENABLE | TIMER_CTRL_CASCADE; - TIMER_CTRL_2 = TIMER_CTRL_ENABLE; -} - -static inline -u32 profile_stop(void) { - TIMER_CTRL_2 = 0; - return (TIMER_DATA_3 << 16) | TIMER_DATA_2; -} - -// -// Input handling. -// - -// Memory address for key input register -#define KEY_INPUTS *((vu16*) 0x04000130) - -// Alias for key pressing bits. -#define KEY_A (1 << 0) -#define KEY_B (1 << 1) -#define KEY_SELECT (1 << 2) -#define KEY_START (1 << 3) -#define KEY_RIGHT (1 << 4) -#define KEY_LEFT (1 << 5) -#define KEY_UP (1 << 6) -#define KEY_DOWN (1 << 7) -#define KEY_R (1 << 8) -#define KEY_L (1 << 9) - -#define KEY_MASK 0x03FF - -// Saving the previous and current key states as globals for now. -static u16 key_curr = 0; -static u16 key_prev = 0; - -static inline void -poll_keys(void) { - key_prev = key_curr; - key_curr = ~KEY_INPUTS & KEY_MASK; -} - -// Returns true if the given key has been pressed at time of calling and was not -// pressed since the previous call. For example, if a key is being held, this -// function will return `true` only on the frame where the key initially -// activated. -static inline u32 -key_pressed(u32 key) { - return (key_curr & key) & ~(key_prev & key); -} - -// Check if the given key is pressed and has been since at least one frame. -static inline u32 -key_hold(u32 key) { - return (key_curr & key) & key_prev & key; -} - -// Check if the given key/button is currently pressed. -#define KEY_PRESSED(key) (~(KEY_INPUTS) & key) - -void -draw_logo(void) { - int side = 60; - int line = 35; - int height = side * 0.5; - int x = SCREEN_WIDTH / 2 - height / 2; - int y = SCREEN_HEIGHT / 2; - - // Draw red triangle. - draw_line(x + height - 1, y - side / 2, x, y - 1, COLOR_RED); - draw_line(x + height - 1, y + side / 2, x, y + 1, COLOR_RED); - draw_line(x + height - 1, y - side / 2 + 1, x, y, COLOR_RED); - draw_line(x + height - 1, y + side / 2 - 1, x, y, COLOR_RED); - - // Draw white triangle. - draw_line(x, y - side / 2, x, y + side / 2, COLOR_WHITE); - draw_line(x + 1, y - side / 2, x + height, y - 1, COLOR_WHITE); - draw_line(x + 1, y + side / 2, x + height, y + 1, COLOR_WHITE); - - // Draw white line at triangle tip. - draw_line(x + height, y - side / 2, x + height, y + side / 2, COLOR_WHITE); - draw_line(x + height + 1, y - side / 2, x + height + 1, y + side / 2, COLOR_WHITE); - - // Double triangle line. - draw_line(x - 1, y - side / 2, x - 1, y + side / 2, COLOR_WHITE); - draw_line(x + 1, y - side / 2 + 1, x + height, y, COLOR_WHITE); - draw_line(x + 1, y + side / 2 - 1, x + height, y, COLOR_WHITE); - - // Draw white lines. - draw_line(x - line, y, x, y, COLOR_WHITE); - draw_line(x + height, y, x + height + line, y, COLOR_WHITE); - draw_line(x - line, y + 1, x, y + 1, COLOR_WHITE); - draw_line(x + height, y + 1, x + height + line, y + 1, COLOR_WHITE); -} - -void -copy_font_to_tile_memory(Tile *tile) { - // Hex to bits translation table. - const u32 conversion_u32[16] = { - 0x00000000, 0x00001000, 0x00000100, 0x00001100, - 0x00000010, 0x00001010, 0x00000110, 0x00001110, - 0x00000001, 0x00001001, 0x00000101, 0x00001101, - 0x00000011, 0x00001011, 0x00000111, 0x00001111, - }; - for (size_t i = 0; i < 250; ++i) { - for (size_t j = 0; j < 8; ++j) { - u8 row = font[i][j]; - u32 tile_idx = 0x00000000; - tile_idx = conversion_u32[row & 0xF] << 16; - tile_idx |= conversion_u32[(row >> 4) & 0xF]; - (tile + i)->data[j] = tile_idx; - } - } -} - - -u32 -unpack_1bb(u8 hex) { - const u32 conversion_u32[16] = { - 0x00000000, 0x00000001, 0x00000010, 0x00000011, - 0x00000100, 0x00000101, 0x00000110, 0x00000111, - 0x00001000, 0x00001001, 0x00001010, 0x00001011, - 0x00001100, 0x00001101, 0x00001110, 0x00001111, - }; - u8 low = hex & 0xF; - u8 high = (hex >> 4) & 0xF; - return (conversion_u32[high] << 16) | conversion_u32[low]; -} - -typedef struct Sprite { - // A unique sprite identifier. - size_t id; - // The number of tiles for a single sprite frame. - size_t n_tiles; - // The starting tile of this sprite. - size_t tile_start; - // The associated palette bank for this sprite. - size_t pal_bank; -} Sprite; - -typedef struct ButtonSprite { - int id; - int x; - int y; - int frame; - BtnState state; -} ButtonSprite; - -typedef struct MultiSprite { - ObjState *sprites; - AnimationEntry **animations; - size_t frame; - size_t n_obj; - size_t n_frames; - BtnState state; -} MultiSprite; - -#define NUM_SPRITES 128 - -Sprite sprites[NUM_SPRITES]; - -// Keeping track of unique sprites and current sprite memory pointer using -// global singletons. -size_t sprite_counter = 0; -size_t sprite_tile_counter = 0; -u32 *sprite_memory = NULL; - -// Loads the sprite data into video memory and initialize the Sprite structure. -size_t -load_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { - memcpy(sprite_memory, sprite_data, 8 * n_tiles * n_frames * sizeof(u32)); - sprite_memory += 8 * n_tiles * n_frames; - Sprite sprite = { - .id = sprite_counter, - .n_tiles = n_tiles, - .tile_start = sprite_tile_counter, - }; - sprite_tile_counter += n_tiles * n_frames; - sprites[sprite_counter] = sprite; - return sprite_counter++; -} - -size_t -load_packed_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { - size_t counter = 0; - for (size_t i = 0; i < 8 * n_tiles * n_frames / 4; ++i) { - u32 hex = sprite_data[i]; - sprite_memory[counter++] = unpack_1bb((hex >> 24) & 0xFF); - sprite_memory[counter++] = unpack_1bb((hex >> 16) & 0xFF); - sprite_memory[counter++] = unpack_1bb((hex >> 8) & 0xFF); - sprite_memory[counter++] = unpack_1bb((hex) & 0xFF); - } - sprite_memory += 8 * n_tiles * n_frames; - Sprite sprite = { - .id = sprite_counter, - .n_tiles = n_tiles, - .tile_start = sprite_tile_counter, - }; - sprite_tile_counter += n_tiles * n_frames; - sprites[sprite_counter] = sprite; - return sprite_counter++; -} - -void -init_button_sprite(MultiSprite *btn) { - for (size_t i = 0; i < btn->n_obj; ++i) { - btn->sprites[i].id = load_packed_sprite_data( - btn->sprites[i].data, - btn->sprites[i].n_tiles, - btn->sprites[i].frames); - btn->sprites[i].base_tile = sprites[btn->sprites[i].id].tile_start; - } -} - -void -button_tick(MultiSprite *btn) { - // Nothing to do here. - if (btn->state == BTN_STATE_IDLE) { - return; - } - - // Reset animation state. - if (btn->state == BTN_STATE_PRESSED && btn->frame != 0) { - btn->frame = 0; - } - - // Continue the animation. - if (btn->state == BTN_STATE_HOLD || btn->state == BTN_STATE_PRESSED ) { - if(btn->frame < btn->n_frames - 1) { - btn->frame++; - } - } - - // Finish the animation and return to idle. - if (btn->state == BTN_STATE_RELEASED) { - if (btn->frame > 0 && btn->frame < btn->n_frames - 1) { - btn->frame++; - } else { - btn->frame = 0; - btn->state = BTN_STATE_IDLE; - } - } - for (size_t i = 0; i < btn->n_obj; ++i) { - AnimationEntry anim_frame = btn->animations[i][btn->frame]; - int x = btn->sprites[i].x + anim_frame.x_offset; - int y = btn->sprites[i].y + anim_frame.y_offset; - int base_tile = btn->sprites[i].base_tile + anim_frame.tile_offset; - - // Clear the previous x/y coordinate and base tiles. - btn->sprites[i].obj_attr_0 &= ~0xFF; - btn->sprites[i].obj_attr_1 &= ~0x1FF; - btn->sprites[i].obj_attr_2 &= ~0x3FF; - - // Update x/y/tile and hidden state from the animations. - btn->sprites[i].obj_attr_0 |= OBJ_Y_COORD(y); - btn->sprites[i].obj_attr_1 |= OBJ_X_COORD(x); - btn->sprites[i].obj_attr_2 |= base_tile; - if (anim_frame.hidden) { - btn->sprites[i].obj_attr_0 |= OBJ_HIDDEN; - } else { - btn->sprites[i].obj_attr_0 &= ~OBJ_HIDDEN; - } - - // Update OBJ attributes. - OBJ_ATTR_0(btn->sprites[i].id) = btn->sprites[i].obj_attr_0; - OBJ_ATTR_1(btn->sprites[i].id) = btn->sprites[i].obj_attr_1; - OBJ_ATTR_2(btn->sprites[i].id) = btn->sprites[i].obj_attr_2; - } -} +// TODO: Cleanup OBJ/OAM memory copying and access. int main(void) { - - // Add colors to the sprite color palette. Tiles with color number 0 are - // treated as transparent. - for (size_t i = 0; i < 16; ++i) { - PAL_BUFFER_SPRITES[i] = COLOR_WHITE; - } - - // Initialize all attributes by disabling rendering. If we don't do this, - // glitches may appear. - for (size_t i = 0; i < 128; ++i) { - OBJ_ATTR_0(i) = (1 << 9); - } - - sprite_tile_counter = 0; - sprite_memory = &TILE_MEM[4][sprite_tile_counter]; - // Load background palette. memcpy(&PAL_BUFFER_BG[0], &bg_palette, 512); memcpy(&TILE_MEM[0][0], bg_data, 3168); @@ -687,445 +27,10 @@ int main(void) { // sequential. DISP_CTRL = DISP_ENABLE_SPRITES | DISP_MODE_0 | DISP_BG_0; - // Initialize the A/B button sprites. - int buttons_x = SCREEN_WIDTH / 2; - int buttons_y = SCREEN_HEIGHT / 2; - - MultiSprite buttons[] = { - // DOWN. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x - 64 - 16, - .y = buttons_y + 29, - .data = &gba_btn_updown_data, - .n_tiles = 4, - .frames = 1, - .obj_attr_0 = 0, - .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 16, - .y = buttons_y + 29 + 11, - .data = &gba_btn_down_shadow_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 16 - 8, - .y = buttons_y + 29 + 17, - .data = &gba_btn_fx_downup, - .n_tiles = 4, - .frames = 4, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - }, - }, - // UP. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x - 64 - 16, - .y = buttons_y + 32 - 18, - .data = &gba_btn_updown_data, - .n_tiles = 4, - .frames = 1, - .obj_attr_0 = 0, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 16, - .y = buttons_y + 32 - 18 + 7, - .data = &gba_btn_up_shadow_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 16 - 8, - .y = buttons_y + 32 - 18 - 7, - .data = &gba_btn_fx_downup, - .n_tiles = 4, - .frames = 4, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_V_FLIP | OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - }, - }, - // LEFT - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x - 64 - 16 - 10, - .y = buttons_y + 32 - 10, - .data = &gba_btn_leftright_data, - .n_tiles = 4, - .frames = 1, - .obj_attr_0 = 0, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 16 - 10, - .y = buttons_y + 32 - 10 + 6, - .data = &gba_btn_leftright_shadow_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 16 - 10 - 6, - .y = buttons_y + 32 - 10 - 8, - .data = &gba_btn_fx_leftright, - .n_tiles = 4, - .frames = 4, - .obj_attr_0 = OBJ_SHAPE_TALL, - .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - }, - }, - // RIGHT. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x - 64 - 16 + 11, - .y = buttons_y + 32 - 10, - .data = &gba_btn_leftright_data, - .n_tiles = 4, - .frames = 1, - .obj_attr_0 = 0, - .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 16 + 11, - .y = buttons_y + 32 - 10 + 6, - .data = &gba_btn_leftright_shadow_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 16 + 11 + 14, - .y = buttons_y + 32 - 10 - 8, - .data = &gba_btn_fx_leftright, - .n_tiles = 4, - .frames = 4, - .obj_attr_0 = OBJ_SHAPE_TALL, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - }, - }, - // L. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x - 64 - 28, - .y = buttons_y - 32 - 20, - .data = &gba_btn_l_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 28, - .y = buttons_y - 32 - 20 + 2, - .data = &gba_btn_lr_shadow_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 64 - 28 - 12, - .y = buttons_y - 32 - 20 - 12, - .data = &gba_btn_fx_lr, - .n_tiles = 4, - .frames = 4, - .obj_attr_0 = OBJ_SHAPE_SQUARE, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - }, - }, - // R. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x + 32 + 20 + 24, - .y = buttons_y - 32 - 20, - .data = &gba_btn_r_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x + 32 + 20 + 24, - .y = buttons_y - 32 - 20 + 2, - .data = &gba_btn_lr_shadow_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x + 32 + 20 + 12 + 24, - .y = buttons_y - 32 - 20 - 12, - .data = &gba_btn_fx_lr, - .n_tiles = 4, - .frames = 4, - .obj_attr_0 = OBJ_SHAPE_SQUARE, - .obj_attr_1 = OBJ_H_FLIP | OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - }, - }, - // A. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation_ab, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x + 32 + 20 + 20, - .y = buttons_y + 32 - 16, - .data = &gba_btn_a_data, - .n_tiles = 4, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_SQUARE, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x + 32 + 20 + 20, - .y = buttons_y + 32 - 16 + 8, - .data = &gba_btn_ab_shadow_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x + 32 + 19 - 7 + 20, - .y = buttons_y + 32 - 23, - .data = &gba_btn_fx_ab, - .n_tiles = 16, - .frames = 3, - .obj_attr_0 = OBJ_SHAPE_SQUARE, - .obj_attr_1 = OBJ_SIZE_BIG, - .obj_attr_2 = 0 - }, - }, - }, - // B. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation_ab, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x + 32 + 20, - .y = buttons_y + 32, - .data = &gba_btn_b_data, - .n_tiles = 4, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_SQUARE, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x + 32 + 20, - .y = buttons_y + 32 + 8, - .data = &gba_btn_ab_shadow_data, - .n_tiles = 2, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x + 32 - 8 + 20, - .y = buttons_y + 33 + 8 - 16, - .data = &gba_btn_fx_ab, - .n_tiles = 16, - .frames = 3, - .obj_attr_0 = OBJ_SHAPE_SQUARE, - .obj_attr_1 = OBJ_SIZE_BIG, - .obj_attr_2 = 0 - }, - }, - }, - // START. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation_startselect, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x - 10 - 14, - .y = buttons_y + 40 + 10, - .data = &gba_btn_startselect_data, - .n_tiles = 2, - .frames = 2, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 18 - 14, - .y = buttons_y + 46 + 10, - .data = &gba_btn_start_text, - .n_tiles = 4, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 19 - 14, - .y = buttons_y + 37 + 10, - .data = &gba_btn_fx_startselect, - .n_tiles = 4, - .frames = 4, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - }, - }, - // SELECT. - { - .frame = 0, - .n_obj = 3, - .n_frames = 8, - .state = BTN_STATE_RELEASED, - .animations = &btn_animation_startselect, - .sprites = &(ObjState[]){ - { - .id = 0, - .x = buttons_x - 10 + 26 - 14, - .y = buttons_y + 40 + 10, - .data = &gba_btn_startselect_data, - .n_tiles = 2, - .frames = 2, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_SMALL, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 18 + 26 - 14, - .y = buttons_y + 46 + 10, - .data = &gba_btn_select_text, - .n_tiles = 4, - .frames = 1, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - { - .id = 0, - .x = buttons_x - 19 + 26 - 14, - .y = buttons_y + 37 + 10, - .data = &gba_btn_fx_startselect, - .n_tiles = 4, - .frames = 4, - .obj_attr_0 = OBJ_SHAPE_WIDE, - .obj_attr_1 = OBJ_SIZE_MID, - .obj_attr_2 = 0 - }, - }, - }, - }; - for (size_t i = 0; i < sizeof(buttons) / sizeof(MultiSprite); ++i) { - init_button_sprite(&buttons[i]); - } + // Initialize sprite button overlay. + init_sprite_pal(0, COLOR_WHITE); + init_sprites(0); + init_button_sprites(); int frame_counter = 0; int x = 0; @@ -1134,77 +39,6 @@ int main(void) { wait_vsync(); poll_keys(); - if (key_pressed(KEY_DOWN)) { - buttons[0].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_DOWN)) { - buttons[0].state = BTN_STATE_HOLD; - } else { - buttons[0].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_UP)) { - buttons[1].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_UP)) { - buttons[1].state = BTN_STATE_HOLD; - } else { - buttons[1].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_LEFT)) { - buttons[2].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_LEFT)) { - buttons[2].state = BTN_STATE_HOLD; - } else { - buttons[2].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_RIGHT)) { - buttons[3].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_RIGHT)) { - buttons[3].state = BTN_STATE_HOLD; - } else { - buttons[3].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_L)) { - buttons[4].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_L)) { - buttons[4].state = BTN_STATE_HOLD; - } else { - buttons[4].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_R)) { - buttons[5].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_R)) { - buttons[5].state = BTN_STATE_HOLD; - } else { - buttons[5].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_A)) { - buttons[6].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_A)) { - buttons[6].state = BTN_STATE_HOLD; - } else { - buttons[6].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_B)) { - buttons[7].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_B)) { - buttons[7].state = BTN_STATE_HOLD; - } else { - buttons[7].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_START)) { - buttons[8].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_START)) { - buttons[8].state = BTN_STATE_HOLD; - } else { - buttons[8].state = BTN_STATE_RELEASED; - } - if (key_pressed(KEY_SELECT)) { - buttons[9].state = BTN_STATE_PRESSED; - } else if (key_hold(KEY_SELECT)) { - buttons[9].state = BTN_STATE_HOLD; - } else { - buttons[9].state = BTN_STATE_RELEASED; - } - if (key_hold(KEY_DOWN)) { y += 3; } @@ -1222,9 +56,7 @@ int main(void) { BG_H_SCROLL_0 = x; BG_V_SCROLL_0 = y; - for (size_t i = 0; i < sizeof(buttons) / sizeof(MultiSprite); ++i) { - button_tick(&buttons[i]); - } + update_button_sprites(); }; 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 @@ +#ifndef GBAEXP_SPRITES_H +#define GBAEXP_SPRITES_H + +#include "common.h" + +u32 +unpack_1bb(u8 hex) { + const u32 conversion_u32[16] = { + 0x00000000, 0x00000001, 0x00000010, 0x00000011, + 0x00000100, 0x00000101, 0x00000110, 0x00000111, + 0x00001000, 0x00001001, 0x00001010, 0x00001011, + 0x00001100, 0x00001101, 0x00001110, 0x00001111, + }; + u8 low = hex & 0xF; + u8 high = (hex >> 4) & 0xF; + return (conversion_u32[high] << 16) | conversion_u32[low]; +} + +typedef struct Sprite { + // A unique sprite identifier. + size_t id; + // The number of tiles for a single sprite frame. + size_t n_tiles; + // The starting tile of this sprite. + size_t tile_start; + // The associated palette bank for this sprite. + size_t pal_bank; +} Sprite; + + +#define NUM_SPRITES 128 +Sprite sprites[NUM_SPRITES]; + +// Keeping track of unique sprites and current sprite memory pointer using +// global singletons. +size_t sprite_counter = 0; +size_t sprite_tile_counter = 0; +u32 *sprite_memory = NULL; + +// Loads the sprite data into video memory and initialize the Sprite structure. +size_t +load_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { + memcpy(sprite_memory, sprite_data, 8 * n_tiles * n_frames * sizeof(u32)); + sprite_memory += 8 * n_tiles * n_frames; + Sprite sprite = { + .id = sprite_counter, + .n_tiles = n_tiles, + .tile_start = sprite_tile_counter, + }; + sprite_tile_counter += n_tiles * n_frames; + sprites[sprite_counter] = sprite; + return sprite_counter++; +} + +size_t +load_packed_sprite_data(u32 *sprite_data, size_t n_tiles, size_t n_frames) { + size_t counter = 0; + for (size_t i = 0; i < 8 * n_tiles * n_frames / 4; ++i) { + u32 hex = sprite_data[i]; + sprite_memory[counter++] = unpack_1bb((hex >> 24) & 0xFF); + sprite_memory[counter++] = unpack_1bb((hex >> 16) & 0xFF); + sprite_memory[counter++] = unpack_1bb((hex >> 8) & 0xFF); + sprite_memory[counter++] = unpack_1bb((hex) & 0xFF); + } + sprite_memory += 8 * n_tiles * n_frames; + Sprite sprite = { + .id = sprite_counter, + .n_tiles = n_tiles, + .tile_start = sprite_tile_counter, + }; + sprite_tile_counter += n_tiles * n_frames; + sprites[sprite_counter] = sprite; + return sprite_counter++; +} + +void +init_sprites(size_t starting_tile) { + // Initialize all attributes by disabling rendering. If we don't do this, + // glitches may appear. + for (size_t i = 0; i < 128; ++i) { + OBJ_ATTR_0(i) = (1 << 9); + } + + sprite_counter = 0; + // Prepare global sprite_memory address. + sprite_memory = &TILE_MEM[4][starting_tile]; +} + +void +init_sprite_pal(size_t starting_index, Color col) { + // Add colors to the sprite color palette. Tiles with color number 0 are + // treated as transparent. + for (size_t i = 0; i < 16; ++i) { + PAL_BUFFER_SPRITES[i + starting_index] = col; + } + +} +#endif // GBAEXP_SPRITES_H -- cgit v1.2.1