Skip to content

Instantly share code, notes, and snippets.

@dicarlo236
Last active February 21, 2019 17:13
// from a pointer to a tile, read a pixel
u8 readSpriteTileAddr(u8* tileAddr, u8 x, u8 y, u8 palette) {
//tileAddr = 0x8180;
assert(x <= 8);
assert(y <= 8);
x = (7 - x);
u8* loAddr = tileAddr + (y*(u16)2);
u8* hiAddr = loAddr + (u16)1;
u8 lo = *(loAddr);
u8 hi = *(hiAddr);
u8 loV = (lo >> x) & (u8)1;
u8 hiV = (hi >> x) & (u8)1;
u8 colorIdx = loV + 2 * hiV;
if(colorIdx == 0) {
return TRANSPARENT_SPRITE;
}
u8 colorID = (palette >> (2 * colorIdx)) & 3;
return colors[colorID];
}
// find the address of the given tile
u8* computeTileAddrPtr(u8 tileIdx, bool tileData) {
if(tileData) {
return globalMemState.vram + 16 * tileIdx;
} else {
if(tileIdx <= 127) {
return globalMemState.vram + 0x1000 + 16 * tileIdx;
} else {
return globalMemState.vram + 16 * (tileIdx);
}
}
}
// main function to render a line of the display.
// this implementation is missing a number of things, including (but not limited to)
// -- proper position of the WINDOW
// -- 16x8 sprites
// -- sprite sorting
// -- 10 sprite limit
void renderLine() {
u8 lcdc = globalMemState.ioRegs[IO_LCDC]; // lcd control register
bool lcdOn = (lcdc >> 7) & (u8)1; // lcd display on?
bool windowTileMap = (lcdc >> 6) & (u8)1; // select tilemap source for window
bool windowEnable = (lcdc >> 5) & (u8)1; // draw window?
bool tileData = (lcdc >> 4) & (u8)1; // select tile data source
bool bgTileMap = (lcdc >> 3) & (u8)1; // select tilemap source for background
bool objSize = (lcdc >> 2) & (u8)1; // pick sprite size (nyi)
bool objEnable = (lcdc >> 1) & (u8)1; // enable sprite renderer
bool bgWinEnable = (lcdc >> 0) & (u8)1; // enable background and window renderer
u16 windowMapAddr = (u16)(windowTileMap ? 0x9c00 : 0x9800);
u16 bgTileMapAddr = (u16)(bgTileMap ? 0x9c00 : 0x9800);
// background renderer
if(lcdOn && bgWinEnable) {
// render background onto framebuffer
u8 pal = globalMemState.ioRegs[IO_BGP]; // color palette
u16 tileMapRowAddr = (u16)(bgTileMapAddr + 32*((((u16)globalVideoState.line +
globalMemState.ioRegs[IO_SCROLLY]) & (u16)255) >> 3)); // address of the row of the tilemap
u8 tileMapColIdx = globalMemState.ioRegs[IO_SCROLLX] >> 3; // column index of the tilemap
u8 yPixOffset = ((u8)globalVideoState.line + globalMemState.ioRegs[IO_SCROLLY]) & (u8)7; // y-pixel of tile
u8 xPixOffset = globalMemState.ioRegs[IO_SCROLLX] & (u8)7; // x-pixel of tile
u8 tileIdx = readByte(tileMapRowAddr + tileMapColIdx); // tile index
// loop over pixels in the line
for(u8 px = 0; px < 160; px++) {
globalVideoState.frameBuffer[xy2px(px,globalVideoState.line)] =
readTilePtr(computeTileAddrPtr(tileIdx, tileData), xPixOffset, yPixOffset, pal);
xPixOffset++; // increment tile pixel
if(xPixOffset == 8) { // if we have overflowed the tile
xPixOffset = 0; // go to the beginning
tileMapColIdx = (tileMapColIdx + 1) & 31; // of the next tile (allow wraparound)
tileIdx = readByte(tileMapRowAddr + tileMapColIdx); // and look up the tile index in the tile map
}
}
}
// window renderer
if(windowEnable) {
u8 pal = globalMemState.ioRegs[IO_BGP]; // palette
u8 wx = globalMemState.ioRegs[IO_WINX]; // location of the window (nyi)
u8 wy = globalMemState.ioRegs[IO_WINY]; // location of the window (nyi)
if(wx > 166 || wy > 143) {
// if the window is out of this range, it is disabled too.
} else {
u16 tileMapRowAddr = windowMapAddr + 32*((((u16)globalVideoState.line)) >> 3); // address of the row of the tilemap
u8 tileMapColIdx = 0; // column index of the tilemap
u8 yPixOffset = ((u8)globalVideoState.line) & (u8)7; // y-pixel of tile
u8 xPixOffset = 0; // x-pixel of tile
u8 tileIdx = readByte(tileMapRowAddr + tileMapColIdx); // tile index
// loop over pixels in the line
for(u8 px = 0; px < 160; px++) {
globalVideoState.frameBuffer[xy2px(px,globalVideoState.line)] =
readTilePtr(computeTileAddrPtr(tileIdx, tileData), xPixOffset, yPixOffset, pal);
xPixOffset++; // increment tile pixel
if(xPixOffset == 8) { // if we have overflowed the tile
xPixOffset = 0; // go to the beginning
tileMapColIdx = (tileMapColIdx + 1) & 31; // of the next tile (allow wraparound, but it shouldn't happen?)
tileIdx = readByte(tileMapRowAddr + tileMapColIdx); // and look up the tile index in the tile map
}
}
}
}
// sprite renderer
if(objEnable) {
for(u16 spriteID = 0; spriteID < 40; spriteID++) {
u16 oamPtr = 0xfe00 + 4 * spriteID; // sprite information table
u8 spriteY = readByte(oamPtr); // y-coordinate of sprite
u8 spriteX = readByte(oamPtr + 1); // x-coordinate of sprite
u8 patternIdx = readByte(oamPtr + 2); // sprite pattern
u8 flags = readByte(oamPtr + 3); // flag bits
bool pri = (flags >> 7) & (u8)1; // priority (transparency stuff)
bool yFlip = (flags >> 6) & (u8)1; // flip around y?
bool xFlip = (flags >> 5) & (u8)1; // flip around x?
bool palID = (flags >> 4) & (u8)1; // palette ID (OBP0/OBP2)
u8 pal = palID ? globalMemState.ioRegs[IO_OBP1] : globalMemState.ioRegs[IO_OBP0];
if(spriteX | spriteY) {
// the sprite coordinates have an offset
u8 spriteStartY = spriteY - 16;
u8 spriteLastY = spriteStartY + 8; // todo 16 row sprites
// reject based on y if the sprite won't be visible in the current line
if(globalVideoState.line < spriteStartY || globalVideoState.line >= spriteLastY) {
continue;
}
// get y px relative to the sprite pattern
u8 tileY = globalVideoState.line - spriteStartY;
if(yFlip) {
tileY = 7 - tileY;
}
assert(tileY < 8);
// loop over the 8 pixels that the sprite is on:
for(u8 tileX = 0; tileX < 8; tileX++) {
u8 xPos = spriteX - 8 + tileX; // position on the screen
if(xPos >= 160) continue; // reject if we go off the end, don't wrap around
u32 fbIdx = xy2px(xPos, globalVideoState.line);
// current color at the screen
u8 old = globalVideoState.frameBuffer[fbIdx];
// get the pixel from the sprite pattern data
u8 tileLookupX = tileX;
if(xFlip) {
tileLookupX = 7 - tileX;
}
u8 tileValue = readSpriteTileAddr(globalMemState.vram + patternIdx * 16, tileLookupX, tileY, pal);
// don't draw transparent
if(tileValue == TRANSPARENT_SPRITE) continue; // (transparent sprites)
if(!pri) {
globalVideoState.frameBuffer[fbIdx] = tileValue;
} else {
if(old == BRIGHTEST_COLOR) {
globalVideoState.frameBuffer[fbIdx] = tileValue;
}
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment