#include "screen.h" /* Each screen layer is 24KB and contains 768 tiles (32*24). Each tile is 32 bytes, and contains 64 pixels (8*8), holding 2 pixels per byte. The upper four bits of a tile byte represent the colour of the right pixel, and the lower four bits represent the colour of the left pixel. The colour is a 4-bit index into a 16-colour RGB15 palette. Four screen layers are used by each physical screen: a foreground layer, a background layer, and then back buffers for each. Screen layers are stored as u16* pointers to raw video memory. Each 'tile slot' of video memory is 16KB, so we need two slots per layer. We can't use memory more efficiently by packing the layers more tightly into the available slots, because we can only draw the contents of a layer by passing a tile slot base address to the video system. We can, however, use the gaps to store our tile map, because each map slot takes only 2KB with a corresponding increase in address granularity. The tile map is shared by all foreground and background layers on a physical screen, and is stored just past the contents of the first screen layer (at the 24KB offset in video memory). Each entry in the map is a 16-bit tile index. The low 10 bits of the value represent the tile index, then h-flip, then v-flip, then a 4-bit palette identifier. We don't flip any tiles, and we always use palette zero. The map is initialised with an incrementing sequence, and then never touched. The memory bank system of the NDS is byzantine and most vexing, with banks being identified by letters A through I, and in sizes of 128/128/128/128/64/16/16/32/16 KB, respectively. The main 2D engine can use banks A/B/C/D/E for BG data (tiles), and the sub 2D engine can use bank C for the same. */ // ------ NDS SCREENS ---------------------------------------------------------- // VRAM addresses for the main 2D engine. NDSScreen main_screen = { .bgv = BG_TILE_RAM(BG_SLOT_FRONT), .fgv = BG_TILE_RAM(FG_SLOT_FRONT), .bg = BG_TILE_RAM(BG_SLOT_BACK), .fg = BG_TILE_RAM(FG_SLOT_BACK), .map = BG_MAP_RAM(MAP_SLOT), .palv = BG_PALETTE, }; // VRAM addresses for the sub 2D engine. NDSScreen sub_screen = { .bgv = BG_TILE_RAM_SUB(BG_SLOT_FRONT), .fgv = BG_TILE_RAM_SUB(FG_SLOT_FRONT), .bg = BG_TILE_RAM_SUB(BG_SLOT_BACK), .fg = BG_TILE_RAM_SUB(FG_SLOT_BACK), .map = BG_MAP_RAM_SUB(MAP_SLOT), .palv = BG_PALETTE_SUB, }; // Initialise the VRAM mappings and tile map for both NDS screens. void init_nds_screens(void) { // Allocate VRAM for screens. videoSetMode(DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | MODE_0_2D); vramSetBankA(VRAM_A_MAIN_BG); videoSetModeSub(DISPLAY_BG0_ACTIVE | DISPLAY_BG1_ACTIVE | MODE_0_2D); vramSetBankC(VRAM_C_SUB_BG); // Configure screen layers to use tile graphics. REG_BG0CNT = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_3 | BG_TILE_BASE(BG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT); REG_BG1CNT = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_2 | BG_TILE_BASE(FG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT); REG_BG0CNT_SUB = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_3 | BG_TILE_BASE(BG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT); REG_BG1CNT_SUB = BG_32x32 | BG_COLOR_16 | BG_PRIORITY_2 | BG_TILE_BASE(FG_SLOT_FRONT) | BG_MAP_BASE(MAP_SLOT); // Populate tile maps. u16 *main_map = BG_MAP_RAM(12); u16 *sub_map = BG_MAP_RAM_SUB(12); for (int i=0; ibg, nds->bgv, LAYER_MEM); dmaCopyWords(0, nds->fg, nds->fgv, LAYER_MEM); for (int i=0; i<16; i++) nds->palv[i] = nds->pal[i]; } } // Erase both buffers and set every colour to black. void ndsscreen_clear(NDSScreen *nds) { if (nds) { dmaFillWords(0, nds->bgv, LAYER_MEM); dmaFillWords(0, nds->fgv, LAYER_MEM); dmaFillWords(0, nds->bg, LAYER_MEM); dmaFillWords(0, nds->fg, LAYER_MEM); for (int i=0; i<16; i++) nds->palv[i] = RGB15(0,0,0); } } // ------ SPRITE BUFFER -------------------------------------------------------- // Reset a sprite buffer. void spritebuf_reset(SpriteBuffer *b) { for (int i=0; i<16; i++) b->mem[i] = 0; b->cached = false; } // Push a byte to a sprite buffer. void spritebuf_push(SpriteBuffer *b, u8 row) { b->mem[b->p++] = row; b->p %= 16; b->cached = FALSE; } // Parse, transform, and cache a 1-bit sprite. void spritebuf_prepare_1bit(SpriteBuffer *b, u8 draw) { u8 l,p,x,y; if (b->cached && draw == b->draw) return; // Parse and transform the sprite if not already cached. switch (draw & 0x07) { case 0x0: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>(7-x) & 1; } }; break; case 0x1: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>( x) & 1; } }; break; case 0x2: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>(7-x) & 1; } }; break; case 0x3: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[y][x] = l>>( x) & 1; } }; break; case 0x4: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>(7-x) & 1; } }; break; case 0x5: p=b->p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>( x) & 1; } }; break; case 0x6: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>(7-x) & 1; } }; break; case 0x7: p=b->p; for (y=0;y<8;y++) { l=b->mem[--p % 16]; for (x=0;x<8;x++) { b->sprite[x][y] = l>>( x) & 1; } }; break; } b->draw = draw; b->cached = true; } // Parse, transform, and cache a 2-bit sprite. void spritebuf_prepare_2bit(SpriteBuffer *b, u8 draw) { u8 l,h,i,p,s,x,y; if (b->cached && draw == b->draw) return; // Parse and transform the sprite if not already cached. switch (draw & 0x07) { case 0x0: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x1: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x2: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x3: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x4: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x5: p=b->p+8; s=p+8; for (y=0;y<8;y++) { l=b->mem[p++ % 16]; h=b->mem[s++ % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x6: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=(7-x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; case 0x7: p=b->p; s=p+8; for (y=0;y<8;y++) { l=b->mem[--p % 16]; h=b->mem[--s % 16]; for (x=0;x<8;x++) { i=( x); b->sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }; break; } b->draw = draw; b->cached = true; } // ------ BEDROCK SCREEN ------------------------------------------------------- // Reset a screen device. void screen_reset(ScreenDevice *screen) { screen->x = 0; screen->y = 0; screen->px = 0; screen->py = 0; screen->selected = 0; spritebuf_reset(&screen->sprite); screen->colour = 0; for (int i=0; i<16; i++) screen->pal[i] = 0; ndsscreen_clear(screen->nds); screen->wake = false; screen->dirty = false; } // Unmap a screen device from a screen. void screen_map_to_none(ScreenDevice *screen) { if (screen->nds) { ndsscreen_clear(screen->nds); screen->nds = NULL; } } // Map a screen device to the main screen. void screen_map_to_main(ScreenDevice *screen) { screen->nds = &main_screen; memcpy(&screen->nds->pal, &screen->pal, 32); screen->wake = true; } // Map a screen device to the sub screen. void screen_map_to_sub(ScreenDevice *screen) { screen->nds = &sub_screen; memcpy(&screen->nds->pal, &screen->pal, 32); screen->wake = true; } // Commit a new colour to the colour palette. void screen_commit_colour(ScreenDevice *screen) { u8 i = screen->colour >> 12 & 0x0f; // Extract 5-bit channel values. u8 r = screen->colour >> 7 & 0x1e; u8 g = screen->colour >> 3 & 0x1e; u8 b = screen->colour << 1 & 0x1e; screen->pal[i] = RGB15(r,g,b); if (screen->nds) screen->nds->pal[i] = RGB15(r,g,b); screen->dirty = true; } // Move the screen cursor. void screen_move_cursor(ScreenDevice *screen, u8 move) { switch (move >> 6) { case 0b00: screen->x += move & 0x3f; return; case 0b01: screen->y += move & 0x3f; return; case 0b10: screen->x -= move & 0x3f; return; case 0b11: screen->y -= move & 0x3f; return; } } // ------ RAW DRAW OPERATIONS -------------------------------------------------- // Draw a pixel to a screen layer. void draw_pixel(u16 *layer, u16 x, u16 y, u8 colour) { if (x < PIXELS_WIDTH && y < PIXELS_HEIGHT) { u32 addr = \ (x >> 2 & 0x0001) + (x << 1 & 0xfff0) + \ (y << 1 & 0x000f) + (y << 6 & 0xfe00); u16 shift = (x & 0x3) << 2; layer[addr] = (layer[addr] & ~(0xf << shift)) | (colour << shift); } } // Fill a screen layer with a solid colour. void fill_layer(u16 *layer, u8 colour) { u8 byte = colour << 4 | colour; u32 word = byte << 24 | byte << 16 | byte << 8 | byte; dmaFillWords(word, layer, LAYER_MEM); } // ------ DRAW OPERATIONS ------------------------------------------------------ void op_draw_pixel(ScreenDevice *screen, u16 *layer, u8 draw) { draw_pixel(layer, screen->x, screen->y, draw&0xf); } void op_fill_layer(ScreenDevice *screen, u16 *layer, u8 draw) { fill_layer(layer, draw&0xf); } void op_draw_sprite(ScreenDevice *screen, u16 *layer, u8 draw) { if (draw & 0x20) { spritebuf_prepare_2bit(&screen->sprite, draw); } else { spritebuf_prepare_1bit(&screen->sprite, draw); } // Parse sprite colours. u8 selected[4] = { screen->selected >> 12 & 0x000f, screen->selected >> 8 & 0x000f, screen->selected >> 4 & 0x000f, screen->selected & 0x000f, }; if (draw & 0x08) { // Draw sprite with transparent background. for (u8 y=0;y<8;y++) { for (u8 x=0;x<8;x++) { u8 i = screen->sprite.sprite[y][x]; if (i) draw_pixel(layer, screen->x+x, screen->y+y, selected[i]); } } } else { // Draw sprite with opaque background. for (u8 y=0;y<8;y++) { for (u8 x=0;x<8;x++) { u8 i = screen->sprite.sprite[y][x]; draw_pixel(layer, screen->x+x, screen->y+y, selected[i]); } } } } void op_draw_line(ScreenDevice *screen, u16 *layer, u8 draw) { s16 x = (s16) screen->x; s16 y = (s16) screen->y; s16 x_end = (s16) screen->px; s16 y_end = (s16) screen->py; s32 dx = abs(x_end - x); s32 dy = -abs(y_end - y); s16 sx = x < x_end ? 1 : -1; s16 sy = y < y_end ? 1 : -1; s32 e1 = dx + dy; if (draw & 0x10) { // Draw 1-bit textured line. spritebuf_prepare_1bit(&screen->sprite, draw); u8 c1 = screen->selected >> 8 & 0xf; u8 c0 = screen->selected >> 12 & 0xf; bool opaque = !(draw & 0x08); while (1) { if (screen->sprite.sprite[y%8][x%8]) { draw_pixel(layer, x, y, c1); } else if (opaque) { draw_pixel(layer, x, y, c0); } if (x == x_end && y == y_end) return; s32 e2 = e1 << 1; if (e2 >= dy) { e1 += dy; x += sx; } if (e2 <= dx) { e1 += dx; y += sy; } } } else { // Draw solid line. u8 c0 = draw & 0xf; while (1) { draw_pixel(layer, x, y, c0); if (x == x_end && y == y_end) return; s32 e2 = e1 << 1; if (e2 >= dy) { e1 += dy; x += sx; } if (e2 <= dx) { e1 += dx; y += sy; } } } } void op_draw_rect(ScreenDevice *screen, u16 *layer, u8 draw) { #define SWAP(x,y) {u8 temp=x; x=y; y=temp;} #define CLAMP(v,m) {v>0x7fff ? 0 : v>m ? m : v} // Get bounding box. u16 l = CLAMP(screen->px, PIXELS_WIDTH -1); u16 r = CLAMP(screen->x , PIXELS_WIDTH -1); u16 t = CLAMP(screen->py, PIXELS_HEIGHT-1); u16 b = CLAMP(screen->y , PIXELS_HEIGHT-1); if (l>r) SWAP(l,r); if (t>b) SWAP(t,b); if (draw & 0x10) { // Draw 1-bit textured rectangle. spritebuf_prepare_1bit(&screen->sprite, draw); u8 c1 = screen->selected >> 8 & 0xf; u8 c0 = screen->selected >> 12 & 0xf; bool opaque = !(draw & 0x08); for (u16 x=l; xsprite.sprite[y%8][x%8]) { draw_pixel(layer, x, y, c1); } else if (opaque) { draw_pixel(layer, x, y, c0); } } } } else { // Draw solid rectangle. u8 c0 = draw & 0xf; for (u16 x=l; xnds) { switch (draw >> 4) { case 0x0: op_draw_pixel(screen, screen->nds->bg, draw); break; case 0x1: op_draw_sprite(screen, screen->nds->bg, draw); break; case 0x2: op_fill_layer(screen, screen->nds->bg, draw); break; case 0x3: op_draw_sprite(screen, screen->nds->bg, draw); break; case 0x4: op_draw_line(screen, screen->nds->bg, draw); break; case 0x5: op_draw_line(screen, screen->nds->bg, draw); break; case 0x6: op_draw_rect(screen, screen->nds->bg, draw); break; case 0x7: op_draw_rect(screen, screen->nds->bg, draw); break; case 0x8: op_draw_pixel(screen, screen->nds->fg, draw); break; case 0x9: op_draw_sprite(screen, screen->nds->fg, draw); break; case 0xA: op_fill_layer(screen, screen->nds->fg, draw); break; case 0xB: op_draw_sprite(screen, screen->nds->fg, draw); break; case 0xC: op_draw_line(screen, screen->nds->fg, draw); break; case 0xD: op_draw_line(screen, screen->nds->fg, draw); break; case 0xE: op_draw_rect(screen, screen->nds->fg, draw); break; case 0xF: op_draw_rect(screen, screen->nds->fg, draw); break; } screen->dirty = true; } screen->px = screen->x; screen->py = screen->y; }