#include "gb-impl.h" BYTE getColour(BYTE colourNum, BYTE palette) { // use the palette to get the colour return (palette >> (colourNum * 2)) & 0x3; } void renderTiles(gb *cpu) { WORD tileData = 0; WORD backgroundMemory = 0; BYTE unsig = 1; BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL); BYTE scrollY = readMemory(cpu, SCROLLY); BYTE scrollX = readMemory(cpu, SCROLLX); BYTE windowY = readMemory(cpu, WINDOWY); BYTE windowX = readMemory(cpu, WINDOWX); if (windowX < 7) { windowX = 7; } windowX -= 7; BYTE currentLine = readMemory(cpu, LCD_SCANLINE_ADRR); BYTE usingWindow = 0; /*The window is enabled and visible?*/ if ((lcd_control & 0x20) != 0 && windowY <= currentLine) usingWindow = 1; // tile area ? if ((lcd_control & 0x10) != 0) tileData = 0x8000; else { // This memory region uses signed bytes tileData = 0x8800; unsig = 0; } BYTE yPos = 0; // yPos is used to calculate which of 32 vertical tiles the // current scanline is drawing if (usingWindow == 0) yPos = scrollY + currentLine; else { yPos = currentLine - windowY; } // which line of the tile is being rendered ? WORD tileRow = (yPos / 8) * 32; BYTE palette = readMemory(cpu, 0xFF47); BYTE colorMap[4] = {getColour(0, palette), getColour(1, palette), getColour(2, palette), getColour(3, palette)}; // Now I have to render the line, pixel by pixel for (BYTE pixel = 0; pixel < 160;) { BYTE xPos = pixel + scrollX; if ((lcd_control & 0x08) != 0) backgroundMemory = 0x9C00; else backgroundMemory = 0x9800; // translate the current x pos to window space if necessary if (usingWindow == 1) { if (pixel >= windowX) { // This means I'm in the window xPos = pixel - windowX; if ((lcd_control & 0x40) != 0) backgroundMemory = 0x9c00; else backgroundMemory = 0x9800; } } // which of the 32 horizontal tiles does this xPos fall within? WORD tileCol = (xPos / 8); WORD tileNum; WORD tileAddrss = backgroundMemory + tileRow + tileCol; if (unsig == 1) tileNum = (BYTE)readMemory(cpu, tileAddrss); else { tileNum = ((SIGNED_WORD)readMemory(cpu, tileAddrss) + (SIGNED_WORD)128) & 0xff; } // deduce where this tile identifier is in memory. WORD tileLocation = tileData + (tileNum * 16); BYTE line = yPos % 8; line *= 2; // each line takes two bytes BYTE data1 = readMemory(cpu, tileLocation + line); BYTE data2 = readMemory(cpu, tileLocation + line + 1); SIGNED_BYTE colourBit = (xPos & 0x7); colourBit -= 7; colourBit *= -1; for(; colourBit >= 0; colourBit--) { BYTE colourNum = (data2 >> (colourBit)) & 0x1; colourNum <<= 1; colourNum |= ((data1 >> (colourBit)) & 0x1); BYTE col = colorMap[colourNum]; cpu->screenData[currentLine][pixel] = col; pixel++; if(pixel >= 160) { break; } } } } void renderSprites(gb *cpu) { BYTE ysize = 8; BYTE currentLine = readMemory(cpu, 0xFF44); BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL); if ((lcd_control & 0x4) != 0) ysize = 16; /*Explore all the sprite table*/ /*Each sprite is formed by 4 bytes: * 0 : y pos - 16 * 1 : x pos - 8 * 2 : data location in memory * 3 : several flags */ for (BYTE index = 0; index < 40 * 4; index += 4) { BYTE yPos = readMemory(cpu, 0xFE00 + index) - 16; if ((currentLine < yPos) || (currentLine >= (yPos + ysize))) continue; BYTE xPos = readMemory(cpu, 0xFE00 + index + 1) - 8; if (xPos > 160) continue; BYTE tileLocation = readMemory(cpu, 0xFE00 + index + 2); BYTE attributes = readMemory(cpu, 0xFE00 + index + 3); BYTE yFlip = attributes & 0x40; BYTE xFlip = attributes & 0x20; WORD colourAddress; if ((attributes & 0x10) != 0) colourAddress = 0xFF49; else colourAddress = 0xFF48; // does this sprite intercept with the scanline? BYTE line = currentLine - yPos; // read the sprite in backwards in the y axis if (yFlip != 0) { line -= ysize; line *= -1; } line *= 2; // same as for tiles WORD dataAddress = (0x8000 + (tileLocation * 16)) + line; BYTE data1 = readMemory(cpu, dataAddress); BYTE data2 = readMemory(cpu, dataAddress + 1); BYTE palette = readMemory(cpu, colourAddress); // its easier to read in from right to left as pixel 0 is // bit 7 in the colour data, pixel 1 is bit 6 etc... for (SIGNED_BYTE tilePixel = 7; tilePixel >= 0; tilePixel--) { SIGNED_BYTE xPix = 0 - tilePixel; xPix += 7; BYTE pixel = xPos + (BYTE)xPix; if (pixel > 160 || currentLine > 144) continue; // check if pixel is hidden behind background if ((attributes & 0x80) != 0 && cpu->screenData[currentLine][pixel] != 0) { continue; } /*Should do some refactoring for avoiding this signed pixel*/ SIGNED_BYTE colourbit = tilePixel; // read the sprite in backwards for the x axis if (xFlip != 0) { colourbit -= 7; colourbit *= -1; } BYTE colourNum = (data2 >> (colourbit)) & 0x1; colourNum <<= 1; colourNum |= ((data1 >> (colourbit)) & 0x1); /*Must ignore the alpha color*/ if (colourNum == 0) continue; BYTE col = getColour(colourNum, palette); /*I can draw the pixel*/ cpu->screenData[currentLine][pixel] = col; } } } void drawScanline(gb *cpu) { BYTE control = readMemory(cpu, LCD_REG_CONTROL); if ((control & 0x1) != 0) renderTiles(cpu); if ((control & 0x2) != 0) renderSprites(cpu); } void updateLCD(gb *cpu) { BYTE status = readMemory(cpu, LCD_REG_STATUS); BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL); /*LCD is disabled*/ if ((lcd_control & 0x80) == 0) { // set the mode to 1 during lcd disabled and reset scanline cpu->clockScanline = 456; cpu->memory[0xFF44] = 0; status &= 252; status |= 0x1; writeMemory(cpu, LCD_REG_STATUS, status); return; } BYTE currentline = readMemory(cpu, LCD_SCANLINE_ADRR); BYTE mode = status & 0x3; BYTE nextMode = 0; BYTE reqInt = 0; // in vblank so set mode to 1 if (currentline >= 144) { nextMode = 1; status &= ~(0x3); status |= 0x1; reqInt = status & 0x10; } else { // mode 2 if (cpu->clockScanline >= 376) { nextMode = 2; status &= ~(0x3); status |= 0x2; reqInt = status & 0x20; } // mode 3 else if (cpu->clockScanline >= 204) { nextMode = 3; status &= ~(0x3); status |= 0x3; } // mode 0 else { nextMode = 0; status &= ~(0x3); reqInt = status & 0x8; } } if ((reqInt != 0) && (mode != nextMode)) raiseInterrupt(cpu, 1); // check the conincidence flag if (readMemory(cpu, 0xFF44) == readMemory(cpu, 0xFF45)) { status |= 0x4; if ((status & 0x40) != 0) raiseInterrupt(cpu, 1); } else { status &= ~(0x4); } writeMemory(cpu, LCD_REG_STATUS, status); } void handleGraphic(gb *cpu, BYTE cycles) { updateLCD(cpu); BYTE lcd_control = cpu->memory[LCD_REG_CONTROL]; if ((lcd_control & 0x80) == 0) { return; } cpu->clockScanline -= cycles; if (cpu->clockScanline <= 0) { BYTE currentline = cpu->memory[LCD_SCANLINE_ADRR]; cpu->clockScanline += 456; /*V-blank interrupt*/ if (currentline == 144) raiseInterrupt(cpu, 0); else if (currentline > 153) cpu->memory[LCD_SCANLINE_ADRR] = 0; else if (currentline < 144) drawScanline(cpu); cpu->memory[LCD_SCANLINE_ADRR]++; } } BYTE getPixelColor(gb *cpu, BYTE x, BYTE y) { BYTE col = cpu->screenData[y][x]; BYTE color = 0; switch (col) { case 0: color = BLACK_COL; break; case 1: color = GRAY_COL; break; case 2: color = LIGHTGRAY_COL; break; case 3: color = WHITE_COL; break; } return color; }