#ifndef GBAEXP_TILES_H #define GBAEXP_TILES_H #include #include #include "common.h" #include "bitmap.h" #include "posprintf.h" typedef enum { TXT_MODE_TILED_BG, TXT_MODE_HYBRID, TXT_MODE_MODE3, } TextMode; typedef struct Font { // A pointer to an area of memory containing font data. // TODO: Should we unpack each char everytime or unpack everything into RAM? // Maybe this should be optional? u16 *data; // The char_map stores the index to the character position within the font // array depending on the ascii number we want to render. This allows // the usage reduced font sets, for example just uppercase letters and // numbers. u8 *char_map; // Width and height of each font character. Only monospaced fonts are // currently supported. u8 char_width; u8 char_height; // The color of this font. Color color; } Font; typedef struct TextEngine { // Currently working on tiled backgrounds only. The X and Y positions // correspond to the tile X and Y starting from the top left of the screen. // For a 240x160 screen, we have 30x20 tiles available. size_t cursor_x; size_t cursor_y; // Pointer to the memory being used for writing to the screen. u16 *memory; // TODO: Support other modes and monospaced fonts should be simple but we // need to keep track of a couple of other variables, which may not be // available for all modes. For example, tile modes can't have a font width // smaller than the tile size, although bigger fonts (For example fonts // composed of multiple tiles) should still be possible. This may not be // a good time investment, but can be done in the future if needed. // The mode controls how the text is rendered and how the memory is managed. TextMode mode; // The font used to render the text. Font font; } TextEngine; static TextEngine text_engine = {0}; static u8 default_char_map[256] = {0}; void txt_putc_tile(char c) { if (c == '\0') { return; } if (c == '\n') { text_engine.cursor_x = 0; text_engine.cursor_y++; } else { text_engine.memory[text_engine.cursor_x + 32 * text_engine.cursor_y] = c; text_engine.cursor_x++; if (text_engine.cursor_x >= 30) { text_engine.cursor_x = 0; text_engine.cursor_y++; } } if (text_engine.cursor_y >= 20) { text_engine.cursor_y = 0; } } void txt_putc_m3(char c) { if (c == '\0') { return; } if (c == '\n') { text_engine.cursor_x = 0; text_engine.cursor_y += text_engine.font.char_height; } else { u8 idx = text_engine.font.char_map[(int)c] * 2; u32 *packed_char = text_engine.font.data; packed_char += idx; Tile tile = {0}; unpack_tiles(packed_char, &tile, 1); int x = text_engine.cursor_x; int y = text_engine.cursor_y; for (size_t i = 0; i < text_engine.font.char_height; ++i) { for (size_t j = 0; j < text_engine.font.char_width; ++j) { if ((tile.row[i] >> 4 * j) & 0x1) { // put_pixel_m4(x + j, y + i, 1, backbuffer); // TODO: Clean this up please. put_pixel_m3(x + j, y + i, text_engine.font.color, FRAMEBUFFER); } } } text_engine.cursor_x += text_engine.font.char_width; if (text_engine.cursor_x >= SCREEN_WIDTH) { text_engine.cursor_x = 0; text_engine.cursor_y += text_engine.font.char_height; } } if (text_engine.cursor_y >= SCREEN_HEIGHT) { text_engine.cursor_y = 0; } } void txt_putc_hybrid(char c) { if (c == '\0') { return; } if (c == '\n') { text_engine.cursor_x = 0; text_engine.cursor_y++; } else { u8 idx = text_engine.font.char_map[(int)c] * 2; u32 *packed_char = text_engine.font.data; packed_char += idx; Tile tile = {0}; unpack_tiles(packed_char, &tile, 1); int x = text_engine.cursor_x; int y = text_engine.cursor_y; Tile *buf = text_engine.memory; buf[x + y * 32] = tile; dirty_tiles[y] |= 1 << x; text_engine.cursor_x += 1; if (text_engine.cursor_x >= 30) { text_engine.cursor_x = 0; text_engine.cursor_y++; } } if (text_engine.cursor_y >= 20) { text_engine.cursor_y = 0; } } void txt_putc(char c) { switch (text_engine.mode) { case TXT_MODE_TILED_BG: { txt_putc_tile(c); } break; case TXT_MODE_MODE3: { txt_putc_m3(c); } break; case TXT_MODE_HYBRID: { txt_putc_hybrid(c); } break; } } static inline void txt_puts(char *msg) { while (*msg) { txt_putc(*msg++); } } void txt_init_tile(size_t bg, Font font, size_t cb_idx) { // The screenblock for the tile map should start after the tile memory // (MEM_VRAM + 0x2000 for 256 characters). Since each screenblock is // composed of 1024 screenblock entries of u16 (2 bytes), we need an // screenblock index offset of 8192 / (1024 * 2) = 4 screen blocks. size_t sb_idx = cb_idx * 8 + 4; // Set the background parameters for the text layer. BG_CTRL(bg) = BG_CHARBLOCK(cb_idx) | BG_SCREENBLOCK(sb_idx) | BG_PRIORITY(3); // Load font data in video memory. Each character is unpacked into a tile of // 8 32bit values (4bpp), meaning that for the full ASCII set of 256 // characters, we need 8192 bytes of VRAM (8 * 4 * 256). text_engine.font = font; unpack_tiles(font.data, &TILE_MEM[cb_idx], 256); // Initialize default values. if (font.color == 0) { font.color = COLOR_WHITE; } // Load palette color. PAL_BUFFER_BG[1] = font.color; // Update text_engine variables. text_engine.memory = SCREENBLOCK_MEM[sb_idx]; } void txt_init_bitmap(TextMode mode, Font font) { // If font_map is NULL, initialize the standard 0-255 character map. if (font.char_map == NULL) { for (size_t i = 0; i < 256; ++i) { default_char_map[i] = i; } font.char_map = &default_char_map; } // Initialize default values if set to zero. if (font.char_width == 0) { font.char_width = 8; } if (font.char_height == 0) { font.char_height = 8; } if (font.color == 0) { font.color = COLOR_WHITE; } // Prepare text engine. text_engine.font = font; text_engine.mode = mode; } void txt_init_hybrid(TextMode mode, Font font, u32 *buf) { // If font_map is NULL, initialize the standard 0-255 character map. if (font.char_map == NULL) { for (size_t i = 0; i < 256; ++i) { default_char_map[i] = i; } font.char_map = &default_char_map; } // Initialize default values if set to zero. if (font.char_width == 0) { font.char_width = 8; } if (font.char_height == 0) { font.char_height = 8; } if (font.color == 0) { font.color = COLOR_WHITE; } // Prepare text engine. text_engine.font = font; text_engine.mode = mode; text_engine.memory = buf; } // Print text to the screen with formatting. #define txt_printf(msg, ...) \ { \ char buf[256] = {0}; \ posprintf(buf, msg, ##__VA_ARGS__); \ txt_puts(buf); \ } void txt_clear_line_tiled(void) { for (size_t i = 0; i < 30; ++i) { text_engine.memory[i + 32 * text_engine.cursor_y] = ' '; } text_engine.cursor_x = 0; } void txt_clear_line_bitmap(void) { // DEBUG: should be on the text struct. Tile *buf = &TILE_MEM[0]; for (size_t i = 0; i < 30; ++i) { buf[i + text_engine.cursor_y * 30] = (Tile){0}; } } void txt_clear_line(void) { switch (text_engine.mode) { case TXT_MODE_TILED_BG: { txt_clear_line_tiled(); } break; case TXT_MODE_MODE3: case TXT_MODE_HYBRID: { txt_clear_line_bitmap(); } break; } } void txt_clear_screen(void) { for (size_t j = 0; j < 20; ++j) { text_engine.cursor_y = j; txt_clear_line(); } text_engine.cursor_x = 0; text_engine.cursor_y = 0; } void txt_position(size_t tile_x, size_t tile_y) { text_engine.cursor_x = tile_x; text_engine.cursor_y = tile_y; } #endif // GBAEXP_TILES_H