/video.cpp Secret
Last active
February 21, 2019 17:13
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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