#include "ppu.h" #include "bd-font.c" /* Copyright (c) 2021 Devine Lu Linvega Copyright (c) 2021 Andrew Alderwick Copyright (c) 2021 Adrian "asie" Siekierka Copyright (c) 2021 Bad Diode Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ #define FG_FRONT ((u32*)(MEM_VRAM)) #define BG_FRONT ((u32*)(MEM_VRAM + KB(20))) #define FG_BACK ((u32*)(MEM_VRAM + KB(44))) #define BG_BACK ((u32*)(MEM_VRAM + KB(64))) #define TILE_MAP ((u32*)(MEM_VRAM + KB(40))) #define FONT_DATA ((u32*)(MEM_VRAM + KB(84))) // Keyboard. #define SPRITE_START_IDX 640 static u32 unpack_icon_lut[256] = { 0x00000000, 0x00000001, 0x00000010, 0x00000011, 0x00000100, 0x00000101, 0x00000110, 0x00000111, 0x00001000, 0x00001001, 0x00001010, 0x00001011, 0x00001100, 0x00001101, 0x00001110, 0x00001111, 0x00010000, 0x00010001, 0x00010010, 0x00010011, 0x00010100, 0x00010101, 0x00010110, 0x00010111, 0x00011000, 0x00011001, 0x00011010, 0x00011011, 0x00011100, 0x00011101, 0x00011110, 0x00011111, 0x00100000, 0x00100001, 0x00100010, 0x00100011, 0x00100100, 0x00100101, 0x00100110, 0x00100111, 0x00101000, 0x00101001, 0x00101010, 0x00101011, 0x00101100, 0x00101101, 0x00101110, 0x00101111, 0x00110000, 0x00110001, 0x00110010, 0x00110011, 0x00110100, 0x00110101, 0x00110110, 0x00110111, 0x00111000, 0x00111001, 0x00111010, 0x00111011, 0x00111100, 0x00111101, 0x00111110, 0x00111111, 0x01000000, 0x01000001, 0x01000010, 0x01000011, 0x01000100, 0x01000101, 0x01000110, 0x01000111, 0x01001000, 0x01001001, 0x01001010, 0x01001011, 0x01001100, 0x01001101, 0x01001110, 0x01001111, 0x01010000, 0x01010001, 0x01010010, 0x01010011, 0x01010100, 0x01010101, 0x01010110, 0x01010111, 0x01011000, 0x01011001, 0x01011010, 0x01011011, 0x01011100, 0x01011101, 0x01011110, 0x01011111, 0x01100000, 0x01100001, 0x01100010, 0x01100011, 0x01100100, 0x01100101, 0x01100110, 0x01100111, 0x01101000, 0x01101001, 0x01101010, 0x01101011, 0x01101100, 0x01101101, 0x01101110, 0x01101111, 0x01110000, 0x01110001, 0x01110010, 0x01110011, 0x01110100, 0x01110101, 0x01110110, 0x01110111, 0x01111000, 0x01111001, 0x01111010, 0x01111011, 0x01111100, 0x01111101, 0x01111110, 0x01111111, 0x10000000, 0x10000001, 0x10000010, 0x10000011, 0x10000100, 0x10000101, 0x10000110, 0x10000111, 0x10001000, 0x10001001, 0x10001010, 0x10001011, 0x10001100, 0x10001101, 0x10001110, 0x10001111, 0x10010000, 0x10010001, 0x10010010, 0x10010011, 0x10010100, 0x10010101, 0x10010110, 0x10010111, 0x10011000, 0x10011001, 0x10011010, 0x10011011, 0x10011100, 0x10011101, 0x10011110, 0x10011111, 0x10100000, 0x10100001, 0x10100010, 0x10100011, 0x10100100, 0x10100101, 0x10100110, 0x10100111, 0x10101000, 0x10101001, 0x10101010, 0x10101011, 0x10101100, 0x10101101, 0x10101110, 0x10101111, 0x10110000, 0x10110001, 0x10110010, 0x10110011, 0x10110100, 0x10110101, 0x10110110, 0x10110111, 0x10111000, 0x10111001, 0x10111010, 0x10111011, 0x10111100, 0x10111101, 0x10111110, 0x10111111, 0x11000000, 0x11000001, 0x11000010, 0x11000011, 0x11000100, 0x11000101, 0x11000110, 0x11000111, 0x11001000, 0x11001001, 0x11001010, 0x11001011, 0x11001100, 0x11001101, 0x11001110, 0x11001111, 0x11010000, 0x11010001, 0x11010010, 0x11010011, 0x11010100, 0x11010101, 0x11010110, 0x11010111, 0x11011000, 0x11011001, 0x11011010, 0x11011011, 0x11011100, 0x11011101, 0x11011110, 0x11011111, 0x11100000, 0x11100001, 0x11100010, 0x11100011, 0x11100100, 0x11100101, 0x11100110, 0x11100111, 0x11101000, 0x11101001, 0x11101010, 0x11101011, 0x11101100, 0x11101101, 0x11101110, 0x11101111, 0x11110000, 0x11110001, 0x11110010, 0x11110011, 0x11110100, 0x11110101, 0x11110110, 0x11110111, 0x11111000, 0x11111001, 0x11111010, 0x11111011, 0x11111100, 0x11111101, 0x11111110, 0x11111111 }; static u32 unpack_icon_lut_flipx[256] = { 0x00000000, 0x10000000, 0x01000000, 0x11000000, 0x00100000, 0x10100000, 0x01100000, 0x11100000, 0x00010000, 0x10010000, 0x01010000, 0x11010000, 0x00110000, 0x10110000, 0x01110000, 0x11110000, 0x00001000, 0x10001000, 0x01001000, 0x11001000, 0x00101000, 0x10101000, 0x01101000, 0x11101000, 0x00011000, 0x10011000, 0x01011000, 0x11011000, 0x00111000, 0x10111000, 0x01111000, 0x11111000, 0x00000100, 0x10000100, 0x01000100, 0x11000100, 0x00100100, 0x10100100, 0x01100100, 0x11100100, 0x00010100, 0x10010100, 0x01010100, 0x11010100, 0x00110100, 0x10110100, 0x01110100, 0x11110100, 0x00001100, 0x10001100, 0x01001100, 0x11001100, 0x00101100, 0x10101100, 0x01101100, 0x11101100, 0x00011100, 0x10011100, 0x01011100, 0x11011100, 0x00111100, 0x10111100, 0x01111100, 0x11111100, 0x00000010, 0x10000010, 0x01000010, 0x11000010, 0x00100010, 0x10100010, 0x01100010, 0x11100010, 0x00010010, 0x10010010, 0x01010010, 0x11010010, 0x00110010, 0x10110010, 0x01110010, 0x11110010, 0x00001010, 0x10001010, 0x01001010, 0x11001010, 0x00101010, 0x10101010, 0x01101010, 0x11101010, 0x00011010, 0x10011010, 0x01011010, 0x11011010, 0x00111010, 0x10111010, 0x01111010, 0x11111010, 0x00000110, 0x10000110, 0x01000110, 0x11000110, 0x00100110, 0x10100110, 0x01100110, 0x11100110, 0x00010110, 0x10010110, 0x01010110, 0x11010110, 0x00110110, 0x10110110, 0x01110110, 0x11110110, 0x00001110, 0x10001110, 0x01001110, 0x11001110, 0x00101110, 0x10101110, 0x01101110, 0x11101110, 0x00011110, 0x10011110, 0x01011110, 0x11011110, 0x00111110, 0x10111110, 0x01111110, 0x11111110, 0x00000001, 0x10000001, 0x01000001, 0x11000001, 0x00100001, 0x10100001, 0x01100001, 0x11100001, 0x00010001, 0x10010001, 0x01010001, 0x11010001, 0x00110001, 0x10110001, 0x01110001, 0x11110001, 0x00001001, 0x10001001, 0x01001001, 0x11001001, 0x00101001, 0x10101001, 0x01101001, 0x11101001, 0x00011001, 0x10011001, 0x01011001, 0x11011001, 0x00111001, 0x10111001, 0x01111001, 0x11111001, 0x00000101, 0x10000101, 0x01000101, 0x11000101, 0x00100101, 0x10100101, 0x01100101, 0x11100101, 0x00010101, 0x10010101, 0x01010101, 0x11010101, 0x00110101, 0x10110101, 0x01110101, 0x11110101, 0x00001101, 0x10001101, 0x01001101, 0x11001101, 0x00101101, 0x10101101, 0x01101101, 0x11101101, 0x00011101, 0x10011101, 0x01011101, 0x11011101, 0x00111101, 0x10111101, 0x01111101, 0x11111101, 0x00000011, 0x10000011, 0x01000011, 0x11000011, 0x00100011, 0x10100011, 0x01100011, 0x11100011, 0x00010011, 0x10010011, 0x01010011, 0x11010011, 0x00110011, 0x10110011, 0x01110011, 0x11110011, 0x00001011, 0x10001011, 0x01001011, 0x11001011, 0x00101011, 0x10101011, 0x01101011, 0x11101011, 0x00011011, 0x10011011, 0x01011011, 0x11011011, 0x00111011, 0x10111011, 0x01111011, 0x11111011, 0x00000111, 0x10000111, 0x01000111, 0x11000111, 0x00100111, 0x10100111, 0x01100111, 0x11100111, 0x00010111, 0x10010111, 0x01010111, 0x11010111, 0x00110111, 0x10110111, 0x01110111, 0x11110111, 0x00001111, 0x10001111, 0x01001111, 0x11001111, 0x00101111, 0x10101111, 0x01101111, 0x11101111, 0x00011111, 0x10011111, 0x01011111, 0x11011111, 0x00111111, 0x10111111, 0x01111111, 0x11111111 }; static u32 dirty_tiles[21] = {0}; void putcolors(u8 *addr) { int i; for(i = 0; i < 4; ++i) { u8 r = (*(addr + i / 2) >> (!(i % 2) << 2)) & 0x0f, g = (*(addr + 2 + i / 2) >> (!(i % 2) << 2)) & 0x0f, b = (*(addr + 4 + i / 2) >> (!(i % 2) << 2)) & 0x0f; PAL_BUFFER_BG[i] = rgb15( (r << 1) | (r >> 3), (g << 1) | (g >> 3), (b << 1) | (b >> 3)); for (size_t j = 0; j < 16; ++j) { PAL_BUFFER_SPRITES[i * 16 + j] = rgb15( (r << 1) | (r >> 3), (g << 1) | (g >> 3), (b << 1) | (b >> 3)); } } } IWRAM_CODE void putpixel(u32 *layer, u16 x, u16 y, u8 color) { if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return; size_t tile_x = x / 8; size_t tile_y = y / 8; size_t start_col = x % 8; size_t start_row = y % 8; size_t pos = (start_row + ((tile_x + tile_y * 32) * 8)); size_t shift = start_col * 4; layer[pos] = (layer[pos] & (~(0xF << shift))) | (color << shift); dirty_tiles[tile_y] |= 1 << tile_x; } IWRAM_CODE void puticn(u32 *layer, u16 x, u16 y, u8 *sprite, u8 color, u8 flipx, u8 flipy) { u8 sprline; u16 v; u32 dirtyflag = (1 << (x >> 3)) | (1 << ((x + 7) >> 3)); u32 layerpos = ((y & 7) + (((x >> 3) + (y >> 3) * 32) * 8)); u32 *layerptr = &layer[layerpos]; u32 shift = (x & 7) << 2; u32 *lut_expand = flipx ? unpack_icon_lut : unpack_icon_lut_flipx; if (flipy) flipy = 7; if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return; if (color != 0x05 && color != 0x0a && color != 0x0f) { u64 mask = ~((u64)0xFFFFFFFF << shift); for (v = 0; v < 8; v++, layerptr++) { if ((y + v) >= SCREEN_HEIGHT) break; sprline = sprite[v ^ flipy]; u64 data = (u64)(lut_expand[sprline] * (color & 3)) << shift; data |= (u64)(lut_expand[sprline ^ 0xFF] * (color >> 2)) << shift; layerptr[0] = (layerptr[0] & mask) | data; layerptr[8] = (layerptr[8] & (mask >> 32)) | (data >> 32); if (((y + v) & 7) == 7) layerptr += (32 - 1) * 8; } } else { for (v = 0; v < 8; v++, layerptr++) { if ((y + v) >= SCREEN_HEIGHT) break; sprline = sprite[v ^ flipy]; u64 mask = ~((u64)(lut_expand[sprline] * 0xF) << shift); u64 data = (u64)(lut_expand[sprline] * (color & 3)) << shift; layerptr[0] = (layerptr[0] & mask) | data; layerptr[8] = (layerptr[8] & (mask >> 32)) | (data >> 32); if (((y + v) & 7) == 7) layerptr += (32 - 1) * 8; } } dirty_tiles[y >> 3] |= dirtyflag; dirty_tiles[(y + 7) >> 3] |= dirtyflag; } IWRAM_CODE void putfontchar(u32 *layer, u16 tile_x, u16 tile_y, u8 ch, u8 color) { u32 pos = (tile_x + tile_y * 32) * 8; u32 *tile_data = &layer[pos]; u32 *font_data = &FONT_DATA[8 * ch]; for (size_t i = 0; i < 8; ++i) { tile_data[i] = font_data[i] * color; } dirty_tiles[tile_y] |= 1 << tile_x; } IWRAM_CODE void putchr(u32 *layer, u16 x, u16 y, u8 *sprite, u8 color, u8 flipx, u8 flipy) { u8 sprline1, sprline2; u16 v; u32 dirtyflag = (1 << (x >> 3)) | (1 << ((x + 7) >> 3)); u32 layerpos = ((y & 7) + (((x >> 3) + (y >> 3) * 32) * 8)); u32 *layerptr = &layer[layerpos]; u32 shift = (x & 7) << 2; u32 *lut_expand = flipx ? unpack_icon_lut : unpack_icon_lut_flipx; if (flipy) flipy = 7; if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return; u64 mask = ~((u64)0xFFFFFFFF << shift); u32 colconst = (color >> 2) * 0x11111111; for (v = 0; v < 8; v++, layerptr++) { if ((y + v) >= SCREEN_HEIGHT) break; sprline1 = sprite[v ^ flipy]; sprline2 = sprite[(v ^ flipy) | 8]; u32 data32 = (lut_expand[sprline1] * (color & 3)) + (lut_expand[sprline2] * ((color & 1) << 1)) + colconst; u64 data = ((u64) (data32 & 0x33333333)) << shift; layerptr[0] = (layerptr[0] & mask) | data; layerptr[8] = (layerptr[8] & (mask >> 32)) | (data >> 32); if (((y + v) & 7) == 7) layerptr += (32 - 1) * 8; } dirty_tiles[y >> 3] |= dirtyflag; dirty_tiles[(y + 7) >> 3] |= dirtyflag; } IWRAM_CODE void flipbuf(Ppu *p) { Tile *mem_fg = FG_FRONT; Tile *mem_bg = BG_FRONT; for (size_t j = 0; j < 20; ++j) { if (dirty_tiles[j] == 0) { continue; } size_t k = 1; for (size_t i = 0; i < 30; ++i, k <<= 1) { if (dirty_tiles[j] & k) { Tile *tile_fg = p->fg; Tile *tile_bg = p->bg; mem_fg[i + j * 32] = tile_fg[i + j * 32]; mem_bg[i + j * 32] = tile_bg[i + j * 32]; } } dirty_tiles[j] = 0; } } typedef struct KeyboardChar { int x; int y; u8 symbol; } KeyboardChar; static u8 cursor_position = 0; #define KEYBOARD_ROW_SIZE 12 #define KEYBOARD_START_TILE_X (30 / 2 - KEYBOARD_ROW_SIZE / 2) #define KEYBOARD_START_TILE_Y (20 / 2 - 3) KeyboardChar keyboard[] = { {0, 0, '!'}, {0, 0, '?'}, {0, 0, '@'}, {0, 0, '#'}, {0, 0, '$'}, {0, 0, '%'}, {0, 0, '^'}, {0, 0, '&'}, {0, 0, '*'}, {0, 0, '"'}, {0, 0, '\''}, {0, 0, 0x7f}, {0, 0, '('}, {0, 0, ')'}, {0, 0, '['}, {0, 0, ']'}, {0, 0, '{'}, {0, 0, '}'}, {0, 0, '<'}, {0, 0, '>'}, {0, 0, '+'}, {0, 0, '-'}, {0, 0, '='}, {0, 0, 0x14}, {0, 0, '0'}, {0, 0, '1'}, {0, 0, '2'}, {0, 0, '3'}, {0, 0, '4'}, {0, 0, '5'}, {0, 0, '6'}, {0, 0, '7'}, {0, 0, '8'}, {0, 0, '9'}, {0, 0, '~'}, {0, 0, 0x18}, {0, 0, 'a'}, {0, 0, 'b'}, {0, 0, 'c'}, {0, 0, 'd'}, {0, 0, 'e'}, {0, 0, 'f'}, {0, 0, 'g'}, {0, 0, 'h'}, {0, 0, 'i'}, {0, 0, 'j'}, {0, 0, '/'}, {0, 0, 0x19}, {0, 0, 'k'}, {0, 0, 'l'}, {0, 0, 'm'}, {0, 0, 'n'}, {0, 0, 'o'}, {0, 0, 'p'}, {0, 0, 'q'}, {0, 0, 'r'}, {0, 0, 's'}, {0, 0, 't'}, {0, 0, '\\'}, {0, 0, 0x1b}, {0, 0, 'u'}, {0, 0, 'v'}, {0, 0, 'w'}, {0, 0, 'x'}, {0, 0, 'y'}, {0, 0, 'z'}, {0, 0, ','}, {0, 0, '.'}, {0, 0, ';'}, {0, 0, ':'}, {0, 0, '_'}, {0, 0, 0x1a}, }; void toggle_keyboard(void) { for (size_t i = 0; i < LEN(keyboard); ++i) { OBJ_ATTR_0(i) ^= OBJ_HIDDEN; } OBJ_ATTR_0(127) ^= OBJ_HIDDEN; } void update_cursor(int pos) { cursor_position = CLAMP(pos, 0, LEN(keyboard) - 1); OBJ_ATTR_0(127) = OBJ_ATTR_0(127) & ~0xFF | OBJ_Y_COORD(keyboard[cursor_position].y); OBJ_ATTR_1(127) = OBJ_ATTR_0(127) & ~0x1FF | OBJ_X_COORD(keyboard[cursor_position].x); } int initppu(Ppu *p, u8 hor, u8 ver, u8 pad) { p->hor = hor; p->ver = ver; p->pad = pad; p->width = (8 * p->hor + p->pad * 2); p->height = (8 * p->ver + p->pad * 2); // Initialize display mode and bg palette. DISP_CTRL = DISP_MODE_0 | DISP_BG_0 | DISP_BG_1 | DISP_OBJ; // Initialize backgrounds. u8 cb_fg = 0; u8 cb_bg = 1; u8 sb_fg = 20; u8 sb_bg = 21; BG_CTRL(0) = BG_CHARBLOCK(cb_fg) | BG_SCREENBLOCK(sb_fg) | BG_PRIORITY(1); BG_CTRL(1) = BG_CHARBLOCK(cb_bg) | BG_SCREENBLOCK(sb_bg) | BG_PRIORITY(2); // Clear front buffer. p->fg = FG_FRONT; p->bg = BG_FRONT; // Use DMA to clear VRAM. u32 fill = 0; dma_fill(p->fg, fill, KB(20), 3); dma_fill(p->bg, fill, KB(20), 3); // Clear back buffer. p->fg = FG_BACK; p->bg = BG_BACK; dma_fill(p->fg, fill, KB(20), 3); dma_fill(p->bg, fill, KB(20), 3); // Initialize default palette. PAL_BUFFER_BG[0] = COLOR_BLACK; PAL_BUFFER_BG[1] = COLOR_WHITE; PAL_BUFFER_BG[2] = COLOR_RED; PAL_BUFFER_BG[3] = COLOR_BLUE; for (size_t i = 0; i < 16; ++i) { PAL_BUFFER_SPRITES[i] = COLOR_BLACK; PAL_BUFFER_SPRITES[1 * 16] = COLOR_WHITE; PAL_BUFFER_SPRITES[2 * 16] = COLOR_RED; PAL_BUFFER_SPRITES[3 * 16] = COLOR_BLUE; } // Initialize background memory map. u16 *mem_map_fg = SCREENBLOCK_MEM[sb_fg]; u16 *mem_map_bg = SCREENBLOCK_MEM[sb_bg]; size_t k = 0; for (size_t i = 0; i < 32 * 20; ++i, ++k) { mem_map_fg[i] = k; mem_map_bg[i] = k + 32 * 4; } // Load font data into VRAM. unpack_tiles(&bd_font, FONT_DATA, 256); // Initialize keyboard sprites. int tile_x = KEYBOARD_START_TILE_X; int tile_y = KEYBOARD_START_TILE_Y; for (size_t i = 0; i < sizeof(keyboard) / sizeof(keyboard[0]); ++i) { keyboard[i].x = tile_x * 8; keyboard[i].y = tile_y * 8; OBJ_ATTR_0(i) = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(keyboard[i].y); OBJ_ATTR_1(i) = OBJ_SIZE_SMALL | OBJ_X_COORD(keyboard[i].x); OBJ_ATTR_2(i) = SPRITE_START_IDX + keyboard[i].symbol | OBJ_PAL_BANK(0); tile_x++; if (tile_x - KEYBOARD_START_TILE_X >= KEYBOARD_ROW_SIZE) { tile_x = KEYBOARD_START_TILE_X; tile_y++; } } OBJ_ATTR_0(127) = OBJ_SHAPE_SQUARE | OBJ_Y_COORD(keyboard[cursor_position].y); OBJ_ATTR_1(127) = OBJ_SIZE_SMALL | OBJ_X_COORD(keyboard[cursor_position].x); OBJ_ATTR_2(127) = SPRITE_START_IDX + 0xdb | OBJ_PAL_BANK(3); return 1; }