// // This Mode 0 renderer provides a way of drawing directly to a framebuffer // (similar to Mode 3 and 4) while retaining the flexibility of using other // backgrounds if needed. It also performs double buffering to avoid tearing // artifacts and tries to only draw tiles that changed on each frame. // // In addition to the frontbuffer (displayed on background 0), a tiled text // layer is displayed on background 1, which can be used for application // development or for debug information. // // These two layers occupy the first and second background charblocks, leaving // the remaining two available for other background layers. There are 14KB of // sprite memory available, since the backbuffer is located at the end of the // VRAM, but if more space is needed it can be moved to the end of the BG // charblocks instead as described below. // #include "renderer.h" #include "text.h" // Keep track of which tiles need to be copied to the frontbuffer. static u32 dirty_tiles[21] = {0}; // TODO: Allow disable bound checking at compile time. #define BOUNDCHECK_SCREEN(X,Y) if ((X) >= SCREEN_WIDTH || (Y) >= SCREEN_HEIGHT) return; IWRAM_CODE void draw_pixel(u16 x, u16 y, u8 color) { BOUNDCHECK_SCREEN(x, y); // Find row position for the given x/y coordinates. 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; // Update backbuffer. size_t shift = start_col * sizeof(u32); BACKBUF[pos] = (BACKBUF[pos] & ~(0xF << shift)) | color << shift; // Mark tile as dirty. dirty_tiles[tile_y] |= 1 << tile_x; } IWRAM_CODE void draw_rect(int x0, int y0, int x1, int y1, u8 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; // TODO: SLOW should be vectorized. for (int i = 0; i <= dx; ++i) { draw_pixel(x0 + i, y0, clr); draw_pixel(x0 + i, y1, clr); } for (int i = 0; i <= dy; ++i) { draw_pixel(x0, y0 + i, clr); draw_pixel(x1, y0 + i, clr); } } IWRAM_CODE void draw_tile(u16 x, u16 y, Tile *tile, bool merge) { BOUNDCHECK_SCREEN(x, y); // Find row position for the given x/y coordinates. size_t tile_x = x / 8; size_t tile_y = y / 8; size_t start_col = x % 8; size_t start_row = y % 8; // Get a pointer to the backbuffer and the tile row. size_t pos = start_row + (tile_x + tile_y * 32) * 8; u32 *backbuffer = &BACKBUF[pos]; u32 *row = tile; // This will blend all colors weirdly if using tiles that contain colors // higher than 1. size_t shift_left = start_col * 4; size_t shift_right = (8 - start_col) * 4; u32 row_mask = merge ? 0 : 0xFFFFFFFF << shift_left; // Draw the tiles. There are 4 possible cases: // 1. The tile is exactly at the tile boundary. // 2. The tile spans 2 tiles horizontally. // 3. The tile spans 2 tiles vertically. // 4. The tile spans 4 tiles. if (start_col == 0 && start_row == 0) { for (size_t i = 0; i < (8 - start_row); i++, backbuffer++) { BOUNDCHECK_SCREEN(x, y + i); backbuffer[0] = (backbuffer[0] & ~row_mask) | row[i]; } dirty_tiles[tile_y] |= 1 << tile_x; } else if (start_row == 0) { for (size_t i = 0; i < 8; i++, backbuffer++) { BOUNDCHECK_SCREEN(x, y + i); backbuffer[0] = (backbuffer[0] & ~row_mask) | (row[i] << shift_left); backbuffer[8] = (backbuffer[8] & row_mask) | (row[i] >> shift_right); } dirty_tiles[tile_y] |= 1 << tile_x; dirty_tiles[tile_y] |= 1 << (tile_x + 1); } else if (start_col == 0) { for (size_t i = 0; i < (8 - start_row); i++, backbuffer++) { BOUNDCHECK_SCREEN(x, y + i); backbuffer[0] = (backbuffer[0] & ~row_mask) | row[i]; } backbuffer += 8 * 31; for (size_t i = (8 - start_row); i < 8; i++, backbuffer++) { BOUNDCHECK_SCREEN(x, y + i); backbuffer[0] = (backbuffer[0] & ~row_mask) | row[i]; } dirty_tiles[tile_y] |= 1 << tile_x; dirty_tiles[tile_y + 1] |= 1 << tile_x; } else { for (size_t i = 0; i < (8 - start_row); i++, backbuffer++) { BOUNDCHECK_SCREEN(x, y + i); backbuffer[0] = (backbuffer[0] & ~row_mask) | (row[i] << shift_left); backbuffer[8] = (backbuffer[8] & row_mask) | (row[i] >> shift_right); } backbuffer += 8 * 31; for (size_t i = (8 - start_row); i < 8; i++, backbuffer++) { BOUNDCHECK_SCREEN(x, y + i); backbuffer[0] = (backbuffer[0] & ~row_mask) | (row[i] << shift_left); backbuffer[8] = (backbuffer[8] & row_mask) | (row[i] >> shift_right); } dirty_tiles[tile_y] |= 1 << tile_x; dirty_tiles[tile_y] |= 1 << (tile_x + 1); dirty_tiles[tile_y + 1] |= 1 << tile_x; dirty_tiles[tile_y + 1] |= 1 << (tile_x + 1); } } IWRAM_CODE void flip_buffer(void) { // Copy dirty tiles from the backbuffer to the frontbuffer. Tile *dst = FRONTBUF; Tile *src = BACKBUF; for (size_t j = 0; j < 20; ++j) { if (dirty_tiles[j] == 0) { continue; } for (size_t i = 0, k = 1; i < 30; ++i, k <<= 1) { if (dirty_tiles[j] & k) { dst[i + j * 32] = src[i + j * 32]; } } dirty_tiles[j] = 0; } } void renderer_init(void) { // Initialize display mode and bg palette. DISP_CTRL = DISP_MODE_0 | DISP_BG_0 | DISP_BG_1 | DISP_OBJ; // Initialize backgrounds. BG_CTRL(0) = BG_CHARBLOCK(0) | BG_SCREENBLOCK(FRONTBUF_SB) | BG_PRIORITY(1); BG_CTRL(1) = BG_CHARBLOCK(1) | BG_SCREENBLOCK(FONT_SB) | BG_PRIORITY(0); // Use DMA to clear front and back buffers as well as the font memory map. dma_fill(FRONTBUF, 0, KB(20), 3); dma_fill(FRONTBUF_TILEMAP, 0, KB(2), 3); dma_fill(BACKBUF, 0, KB(20), 3); dma_fill(FONT_DATA, 0, KB(8), 3); dma_fill(FONT_TILEMAP, FONT_OFFSET, KB(2), 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; PAL_BUFFER_BG[4] = COLOR_CYAN; PAL_BUFFER_BG[5] = COLOR_GREY; // Initialize background memory map for frontbuffer and font backgorund. for (size_t i = 0; i < 32 * 20; ++i) { FRONTBUF_TILEMAP[i] = i; } // Initialize text engine. txt_init(FONT_DATA, FONT_TILEMAP, FONT_OFFSET); }