#include #include #include #include "video.h" #include "tiles.h" /*** T I L E S ***/ // Tiles are 16x16 bitmaps, stored as arrays of words. // Each tile has 4 or 5 planes (depending on whether it is a tile or sprite) // which are stored adjacant to each other; ie. a 16-word array of blue, // followed by a 16-word array of green, etc. // Tiles in RAM are stored byte-swapped to aid in fast bit-shifting, and must // be byte-swapped before being written to video memory. // Because bit-shifting operations happen on little-endian words: // 01234567 89ABCDEF << 3 => 34567XXX BCDEF012 // which is wrong. So instead we do: // 89ABCDEF 01234567 << 3 => BCDEFXXX 3456789A byteswap => 3456789A BCDEFXXX void tile_init() { setLogicalWidth(PAGE_STRIDE >> 1); } void blitTile(unsigned int offsetFrom, unsigned int offsetTo) { int y; for (y = 0; y < 16; y ++) { VID[offsetTo] = VID[offsetFrom ++]; VID[offsetTo + 1] = VID[offsetFrom ++]; offsetTo += PAGE_STRIDE; } } #define D_NOTHING 0x80 #define D_BGTILE 0x81 #define isBufIndex(d) (!((d) & 0x80)) #define NUM_BUFFERS 32 #define nextBufferIndex(i) ((i + 1) % 32) #define BUF_WSTRIDE 16 #define BUF_WSIZE (BUF_WSTRIDE * 4) typedef struct { unsigned int w; unsigned int h; int scrollX; int scrollY; unsigned int pageOffset[2]; unsigned char dirty[2][PAGE_TILES_COUNT]; unsigned int tilesOffset; unsigned int *memTiles; unsigned char *map; unsigned int buffer[NUM_BUFFERS][BUF_WSIZE]; unsigned int bufferOffset[NUM_BUFFERS]; unsigned char currentPage; unsigned char nextBuffer; unsigned char firstBuffer; } TiledScreen_t; TiledScreen_t screen = { 0, 0, 0, 0, { 0x0600, 0x2B00 }, 0, 0, NULL, NULL, 0, 0, 0, 0, 0 }; void loadTiles(unsigned int tilesOffset, unsigned int *memTiles) { screen.tilesOffset = tilesOffset; screen.memTiles = memTiles; } void loadMap(unsigned char *map, unsigned int w, unsigned int h) { screen.map = map; screen.w = w; screen.h = h; memset(screen.dirty, D_BGTILE, PAGE_TILES_COUNT * 2); } int prepareBuffer(int pageX, int pageY) { unsigned char *dirty = &screen.dirty[screen.currentPage][pageX + (pageY * PAGE_TILES_W)]; if (!isBufIndex(*dirty)) { unsigned int startX = screen.scrollX >> 4; unsigned int startY = screen.scrollY >> 4; unsigned char tile = screen.map[startX + pageX + ((startY + pageY) * screen.w)]; unsigned char ibuffer = screen.nextBuffer; screen.nextBuffer = nextBufferIndex(ibuffer); *dirty = ibuffer; memcpy(screen.buffer[ibuffer], &screen.memTiles[tile * BUF_WSIZE], BUF_WSIZE << 1); screen.bufferOffset[ibuffer] = screen.pageOffset[screen.currentPage] + (pageX << 1) + (pageY * PAGE_STRIDE * 16); } return *dirty; } void drawSpriteToBuf(unsigned int *sprite, int pageX, int pageY, int shift, int yStart) { unsigned int *buf, *mask; unsigned int maskval; int y, h, plane; if (pageX < 0 || pageY < 0 || pageX >= PAGE_TILES_W || pageY >= PAGE_TILES_H || shift >= 16 || shift <= -16 || yStart <= -16 || yStart >= 16) { return; } buf = screen.buffer[prepareBuffer(pageX, pageY)]; if (yStart < 0) { sprite = &sprite[-yStart]; h = yStart + 16; } else { buf = &buf[yStart]; h = 16 - yStart; } mask = &sprite[BUF_WSTRIDE * 4]; if (shift < 0) { shift = -shift; for (plane = 0; plane < 4; plane ++) { for (y = 0; y < h; y ++) { maskval = mask[y] << shift; buf[y] = (buf[y] & ~maskval) | ((sprite[y] << shift) & maskval); } sprite += BUF_WSTRIDE; buf += BUF_WSTRIDE; } } else { for (plane = 0; plane < 4; plane ++) { for (y = 0; y < h; y ++) { maskval = mask[y] >> shift; buf[y] = (buf[y] & ~maskval) | ((sprite[y] >> shift) & maskval); } sprite += BUF_WSTRIDE; buf += BUF_WSTRIDE; } } } void drawSprite(unsigned int *sprite, int x, int y) { int pageX = (int)(x - (screen.scrollX & 0xfff0)) >> 4; int pageY = (int)(y - (screen.scrollY & 0xfff0)) >> 4; int pageOffsetX = x & 0x0f; int pageOffsetY = y & 0x0f; drawSpriteToBuf(sprite, pageX, pageY, pageOffsetX, pageOffsetY); drawSpriteToBuf(sprite, pageX + 1, pageY, pageOffsetX - 16, pageOffsetY); drawSpriteToBuf(sprite, pageX, pageY + 1, pageOffsetX, pageOffsetY - 16); drawSpriteToBuf(sprite, pageX + 1, pageY + 1, pageOffsetX - 16, pageOffsetY - 16); } void scroll(int newX, int newY) { newX = min(max(newX, 0), (screen.w << 4) - 320); newY = min(max(newY, 0), (screen.h << 4) - 200); if ((screen.scrollX & 0xfff0) != (newX & 0xfff0) || (screen.scrollY & 0xfff0) != (newY & 0xfff0)) { int mapX, mapY; unsigned char page; for (page = 0; page < 2; page ++) { int mapOffsetOld = (screen.scrollX >> 4) + ((screen.scrollY >> 4) * screen.w); int mapOffsetNew = (newX >> 4) + ((newY >> 4) * screen.w); unsigned char *dirty = screen.dirty[page]; for (mapY = 0; mapY < PAGE_TILES_H; mapY ++) { for (mapX = 0; mapX < PAGE_TILES_W; mapX ++) { if (*dirty != D_NOTHING || screen.map[mapOffsetOld + mapX] != screen.map[mapOffsetNew + mapX]) { *dirty = D_BGTILE; } dirty ++; } mapOffsetNew += screen.w; mapOffsetOld += screen.w; } } } screen.scrollX = newX; screen.scrollY = newY; } void drawScreen() { unsigned int startX = screen.scrollX >> 4; unsigned int startY = screen.scrollY >> 4; unsigned int offsetX = screen.scrollX - (startX << 4); unsigned int offsetY = screen.scrollY - (startY << 4); unsigned int drawOffset = screen.pageOffset[screen.currentPage]; unsigned int scrollOffset = drawOffset + (offsetX >> 3) + (offsetY * PAGE_STRIDE); unsigned char *dirty = screen.dirty[screen.currentPage]; unsigned int x, y, di, plane, bmp; setAllPlanes(); setWriteMode(1); di = 0; for (y = startY; y < startY + PAGE_TILES_H; y ++) { for (x = startX; x < startX + PAGE_TILES_W; x ++) { if (dirty[di++] == D_BGTILE) { blitTile( screen.tilesOffset + (screen.map[x + (y * screen.w)] << 5), drawOffset); } drawOffset += 2; } drawOffset += PAGE_STRIDE * 15; } setWriteMode(0); for(plane = 0; plane < 4; plane ++) { setPlane(plane); for (di = screen.firstBuffer; di != screen.nextBuffer; di = nextBufferIndex(di)) { drawOffset = screen.bufferOffset[di] >> 1; for (y = 0; y < 16; y ++) { bmp = screen.buffer[di][y + (BUF_WSTRIDE * plane)]; WVID[drawOffset] = (bmp << 8) | (bmp >> 8); drawOffset += PAGE_STRIDE >> 1; } } } setAllPlanes(); setDisplayOffset(scrollOffset); setHorizontalPan(screen.scrollX & 0x07); screen.currentPage ^= 1; screen.firstBuffer = screen.nextBuffer; for (di = 0; di < PAGE_TILES_COUNT; di ++) { dirty[di] = isBufIndex(dirty[di]) ? D_BGTILE : D_NOTHING; } }